Arahan sudut - kapan dan bagaimana menggunakan kompilasi, pengontrol, pra-tautan dan pasca-tautan [ditutup]

451

Saat menulis arahan Angular, seseorang dapat menggunakan salah satu dari fungsi berikut untuk memanipulasi perilaku DOM, konten dan tampilan elemen di mana arahan dinyatakan:

  • menyusun
  • pengontrol
  • pra-tautan
  • posting-tautan

Tampaknya ada beberapa kebingungan mengenai fungsi mana yang harus digunakan. Pertanyaan ini meliputi:

Dasar-dasar arahan

Berfungsi alam, lakukan dan jangan

Pertanyaan-pertanyaan Terkait:

Izhaki
sumber
27
Apa itu apa?
haimlit
2
@Ian See: Overloading operator . Pada dasarnya ini ditujukan untuk wiki komunitas. Terlalu banyak jawaban untuk pertanyaan terkait hanya sebagian, tidak memberikan gambaran lengkap.
Izhaki
8
Ini adalah konten yang hebat, tetapi kami meminta agar semua yang ada di sini disimpan dalam format tanya jawab. Mungkin Anda ingin memecah ini menjadi beberapa pertanyaan terpisah dan kemudian menautkannya dari tag wiki?
Flexo
57
Meskipun posting ini di luar topik dan dalam bentuk blog, itu sangat berguna dalam memberikan penjelasan mendalam tentang arahan Angular. Tolong jangan hapus posting ini, admin!
Penafsiran
12
Jujur, saya bahkan tidak repot-repot dengan dokumen asli. Posting stackoverflow atau blog biasanya membuat saya pergi dalam hitungan detik, versus 15-30 menit merobek rambut saya mencoba memahami dokumen asli.
David

Jawaban:

168

Dalam urutan mana fungsi direktif dijalankan?

Untuk arahan tunggal

Berdasarkan plunk berikut , pertimbangkan markup HTML berikut:

<body>
    <div log='some-div'></div>
</body>

Dengan deklarasi direktif berikut:

myApp.directive('log', function() {

    return {
        controller: function( $scope, $element, $attrs, $transclude ) {
            console.log( $attrs.log + ' (controller)' );
        },
        compile: function compile( tElement, tAttributes ) {
            console.log( tAttributes.log + ' (compile)'  );
            return {
                pre: function preLink( scope, element, attributes ) {
                    console.log( attributes.log + ' (pre-link)'  );
                },
                post: function postLink( scope, element, attributes ) {
                    console.log( attributes.log + ' (post-link)'  );
                }
            };
         }
     };  

});

Output konsol adalah:

some-div (compile)
some-div (controller)
some-div (pre-link)
some-div (post-link)

Kita dapat melihat bahwa compiledieksekusi terlebih dahulu, lalu controller, lalu pre-linkdan terakhir post-link.

Untuk arahan bersarang

Catatan: Berikut ini tidak berlaku untuk arahan yang membuat anak-anak mereka dalam fungsi tautan mereka. Cukup banyak arahan Angular melakukannya (seperti ngIf, ngRepeat, atau arahan dengan transclude). Arahan-arahan ini akan memiliki linkfungsi mereka dipanggil sebelum arahan anak mereka compiledipanggil.

Markup HTML asli sering dibuat dari elemen bersarang, masing-masing dengan arahannya sendiri. Seperti di markup berikut (lihat plunk ):

<body>
    <div log='parent'>
        <div log='..first-child'></div>
        <div log='..second-child'></div>
    </div>
</body>

Output konsol akan terlihat seperti ini:

// The compile phase
parent (compile)
..first-child (compile)
..second-child (compile)

// The link phase   
parent (controller)
parent (pre-link)
..first-child (controller)
..first-child (pre-link)
..first-child (post-link)
..second-child (controller)
..second-child (pre-link)
..second-child (post-link)
parent (post-link)

Kita dapat membedakan dua fase di sini - fase kompilasi dan fase tautan .

Fase kompilasi

Ketika DOM dimuat, Angular memulai fase kompilasi, di mana ia melintasi markup top-down, dan memanggil compilesemua arahan. Secara grafis, kita dapat mengekspresikannya seperti ini:

Gambar yang menggambarkan lingkaran kompilasi untuk anak-anak

Mungkin penting untuk menyebutkan bahwa pada tahap ini, templat yang didapat fungsi kompilasi adalah templat sumber (bukan templat contoh).

Fase tautan

Contoh DOM seringkali hanya hasil dari templat sumber yang diberikan ke DOM, tetapi mereka dapat dibuat oleh ng-repeat, atau diperkenalkan dengan cepat.

Setiap kali instance baru elemen dengan arahan diberikan ke DOM, fase tautan dimulai.

Dalam fase ini, Angular memanggil controller,, pre-linkmengulangi anak-anak, dan memanggil post-linksemua arahan, seperti:

Ilustrasi yang menunjukkan langkah-langkah fase tautan

Izhaki
sumber
5
@ lzhaki Diagram alir terlihat bagus. Pikiran untuk membagikan nama alat bagan? :)
merlin
1
@merlin Saya telah menggunakan OmniGraffle (tapi bisa menggunakan ilustrator atau inkscape - selain kecepatan, tidak ada yang OmniGraffle lakukan lebih baik daripada alat charting lainnya sejauh ilustrasi ini diperhatikan).
Izhaki
2
@ Anant's plunker menghilang jadi inilah yang baru: plnkr.co/edit/kZZks8HN0iFIY8ZaKJkA?p=preview Buka konsol JS untuk melihat laporan log
MENGAPA ini tidak benar ketika ng-repeat digunakan untuk arahan anak-anak ??? Lihat plunk
Luckylooke
@Luckylooke Plunk Anda tidak punya anak dengan arahan di bawah ng-repeat (yaitu, apa yang diulang adalah template dengan arahan. Jika ya, Anda akan melihat bahwa kompilasi mereka hanya dipanggil setelah tautan ng-repeat.
Izhaki
90

Apa lagi yang terjadi antara pemanggilan fungsi ini?

Berbagai fungsi direktif dijalankan dari dalam dua fungsi sudut lain yang disebut $compile(di mana direktif compiledijalankan) dan fungsi internal disebut nodeLinkFn(di mana direktif controller, preLinkdan postLinkdijalankan). Berbagai hal terjadi dalam fungsi sudut sebelum dan sesudah fungsi direktif dipanggil. Mungkin yang paling menonjol adalah rekursi anak. Ilustrasi sederhana berikut ini menunjukkan langkah-langkah utama dalam fase kompilasi dan tautan:

Ilustrasi yang menunjukkan kompilasi sudut dan fase tautan

Untuk mendemonstrasikan langkah-langkah ini, mari gunakan markup HTML berikut:

<div ng-repeat="i in [0,1,2]">
    <my-element>
        <div>Inner content</div>
    </my-element>
</div>

Dengan arahan berikut:

myApp.directive( 'myElement', function() {
    return {
        restrict:   'EA',
        transclude: true,
        template:   '<div>{{label}}<div ng-transclude></div></div>'
    }
});

Menyusun

The compileAPI terlihat seperti begitu:

compile: function compile( tElement, tAttributes ) { ... }

Seringkali parameter diawali dengan tuntuk menandakan elemen dan atribut yang disediakan adalah orang-orang dari templat sumber, bukan dari contoh.

Sebelum panggilan ke compilekonten yang ditransklusikan (jika ada) dihapus, dan templat diterapkan ke markup. Dengan demikian, elemen yang disediakan untuk compilefungsi akan terlihat seperti ini:

<my-element>
    <div>
        "{{label}}"
        <div ng-transclude></div>
    </div>
</my-element>

Perhatikan bahwa konten yang ditransklusikan tidak dimasukkan kembali pada saat ini.

Mengikuti panggilan ke arahan .compile, Angular akan melintasi semua elemen anak, termasuk yang mungkin baru saja diperkenalkan oleh arahan (elemen template, misalnya).

Pembuatan instance

Dalam kasus kami, tiga contoh templat sumber di atas akan dibuat (oleh ng-repeat). Dengan demikian, urutan berikut akan dieksekusi tiga kali, sekali per instance.

Pengendali

The controllerAPI melibatkan:

controller: function( $scope, $element, $attrs, $transclude ) { ... }

Memasuki fase tautan, fungsi tautan yang dikembalikan via $compilesekarang disediakan dengan cakupan.

Pertama, fungsi tautan membuat lingkup anak ( scope: true) atau lingkup terisolasi ( scope: {...}) jika diminta.

Kontroler kemudian dieksekusi, disediakan dengan cakupan elemen instance.

Pra-tautan

The pre-linkAPI terlihat seperti begitu:

function preLink( scope, element, attributes, controller ) { ... }

Sebenarnya tidak ada yang terjadi antara panggilan ke direktif .controllerdan .preLinkfungsi. Angular masih memberikan rekomendasi bagaimana masing-masing harus digunakan.

Setelah .preLinkpanggilan, fungsi tautan akan melintasi setiap elemen anak - memanggil fungsi tautan yang benar dan melampirkan padanya lingkup saat ini (yang berfungsi sebagai lingkup induk untuk elemen anak).

Posting-tautan

The post-linkAPI mirip dengan yang dari pre-linkfungsi:

function postLink( scope, element, attributes, controller ) { ... }

Mungkin perlu diperhatikan bahwa sekali .postLinkfungsi direktif dipanggil, proses penghubung semua elemen anak-anaknya telah selesai, termasuk semua fungsi anak-anak .postLink.

Ini berarti bahwa pada saat .postLinkdipanggil, anak-anak sudah 'hidup' sudah siap. Ini termasuk:

  • pengikatan data
  • transklusi diterapkan
  • ruang lingkup terlampir

Template pada tahap ini akan terlihat seperti ini:

<my-element>
    <div class="ng-binding">
        "{{label}}"
        <div ng-transclude>                
            <div class="ng-scope">Inner content</div>
        </div>
    </div>
</my-element>
Izhaki
sumber
3
Bagaimana Anda membuat gambar ini?
Royi Namir
6
@RoyiNamir Omnigraffle.
Izhaki
43

Bagaimana cara mendeklarasikan berbagai fungsi?

Kompilasi, Pengontrol, Pra-tautan & Pasca-tautan

Jika seseorang ingin menggunakan keempat fungsi, arahan akan mengikuti formulir ini:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) {
            // Controller code goes here.
        },
        compile: function compile( tElement, tAttributes, transcludeFn ) {
            // Compile code goes here.
            return {
                pre: function preLink( scope, element, attributes, controller, transcludeFn ) {
                    // Pre-link code goes here
                },
                post: function postLink( scope, element, attributes, controller, transcludeFn ) {
                    // Post-link code goes here
                }
            };
        }
    };  
});

Perhatikan bahwa kompilasi mengembalikan objek yang berisi fungsi pre-link dan post-link; dalam istilah Angular kita mengatakan fungsi kompilasi mengembalikan fungsi templat .

Kompilasi, Kontroler & Tautan-posting

Jika pre-linktidak diperlukan, fungsi kompilasi dapat dengan mudah mengembalikan fungsi post-link alih-alih objek definisi, seperti:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) {
            // Controller code goes here.
        },
        compile: function compile( tElement, tAttributes, transcludeFn ) {
            // Compile code goes here.
            return function postLink( scope, element, attributes, controller, transcludeFn ) {
                    // Post-link code goes here                 
            };
        }
    };  
});

Terkadang, seseorang ingin menambahkan compilemetode, setelah metode (posting) linkditentukan. Untuk ini, seseorang dapat menggunakan:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) {
            // Controller code goes here.
        },
        compile: function compile( tElement, tAttributes, transcludeFn ) {
            // Compile code goes here.

            return this.link;
        },
        link: function( scope, element, attributes, controller, transcludeFn ) {
            // Post-link code goes here
        }

    };  
});

Controller & Post-link

Jika tidak ada fungsi kompilasi yang diperlukan, seseorang dapat melewatkan deklarasi sama sekali dan menyediakan fungsi post-link di bawah linkproperti objek konfigurasi direktif:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) {
            // Controller code goes here.
        },
        link: function postLink( scope, element, attributes, controller, transcludeFn ) {
                // Post-link code goes here                 
        },          
    };  
});

Tidak ada pengontrol

Dalam salah satu contoh di atas, seseorang dapat dengan mudah menghapus controllerfungsi jika tidak diperlukan. Jadi misalnya, jika hanya post-linkfungsi yang diperlukan, seseorang dapat menggunakan:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        link: function postLink( scope, element, attributes, controller, transcludeFn ) {
                // Post-link code goes here                 
        },          
    };  
});
Izhaki
sumber
31

Apa perbedaan antara templat sumber dan templat contoh ?

Fakta bahwa Angular memungkinkan manipulasi DOM berarti bahwa markup input ke dalam proses kompilasi terkadang berbeda dari output. Khususnya, beberapa markup input dapat dikloning beberapa kali (seperti dengan ng-repeat) sebelum diberikan ke DOM.

Terminologi sudut agak tidak konsisten, tetapi masih membedakan antara dua jenis markup:

  • Templat sumber - markup yang akan dikloning, jika perlu. Jika dikloning, markup ini tidak akan diberikan ke DOM.
  • Templat contoh - markup aktual yang akan diberikan ke DOM. Jika kloning terlibat, setiap instance akan menjadi klon.

Markup berikut menunjukkan ini:

<div ng-repeat="i in [0,1,2]">
    <my-directive>{{i}}</my-directive>
</div>

Sumber html mendefinisikan

    <my-directive>{{i}}</my-directive>

yang berfungsi sebagai templat sumber.

Tetapi karena dibungkus dalam suatu ng-repeatarahan, templat sumber ini akan dikloning (3 kali dalam kasus kami). Klon ini adalah contoh template, masing-masing akan muncul di DOM dan terikat ke ruang lingkup yang relevan.

Izhaki
sumber
23

Kompilasi fungsi

compileFungsi masing-masing direktif hanya dipanggil sekali, saat Angular bootstraps.

Secara resmi, ini adalah tempat untuk melakukan manipulasi template (sumber) yang tidak melibatkan lingkup atau pengikatan data.

Terutama, ini dilakukan untuk keperluan optimasi; pertimbangkan markup berikut:

<tr ng-repeat="raw in raws">
    <my-raw></my-raw>
</tr>

The <my-raw>direktif akan membuat satu set tertentu DOM markup. Jadi kita bisa:

  • Izinkan ng-repeatuntuk menduplikasi templat sumber ( <my-raw>), dan kemudian memodifikasi markup setiap templat contoh (di luar compilefungsi).
  • Ubah templat sumber untuk melibatkan markup yang diinginkan (dalam compilefungsi), dan kemudian memungkinkan ng-repeatuntuk menduplikatnya.

Jika ada 1000 item dalam rawskoleksi, opsi yang terakhir mungkin lebih cepat dari yang sebelumnya.

Melakukan:

  • Memanipulasi markup sehingga berfungsi sebagai templat untuk instance (klon).

Tidak

  • Lampirkan penangan acara.
  • Periksa elemen anak.
  • Atur pengamatan pada atribut.
  • Atur jam tangan pada ruang lingkup.
Izhaki
sumber
20

Fungsi pengontrol

controllerFungsi masing-masing direktif dipanggil setiap kali elemen terkait baru dipakai.

Secara resmi, controllerfungsinya adalah di mana satu:

  • Menentukan logika pengontrol (metode) yang dapat dibagi di antara pengontrol.
  • Memulai variabel cakupan.

Sekali lagi, penting untuk diingat bahwa jika arahan melibatkan lingkup terisolasi, properti di dalamnya yang mewarisi dari lingkup induk belum tersedia.

Melakukan:

  • Tentukan logika pengontrol
  • Memulai variabel cakupan

Tidak:

  • Periksa elemen anak (mereka mungkin belum diberikan, terikat pada ruang lingkup, dll.).
Izhaki
sumber
Senang bahwa Anda menyebutkan Pengendali dalam direktif adalah tempat yang bagus untuk menginisialisasi ruang lingkup. Saya kesulitan menemukan itu.
jsbisht
1
Kontroler TIDAK "Memulai ruang lingkup", itu hanya mengakses ruang lingkup yang sudah dimulai terlepas dari itu.
Dmitri Zaitsev
@DmitriZaitsev memperhatikan detail dengan baik. Saya telah mengubah teksnya.
Izhaki
19

Fungsi pasca-tautan

Ketika post-linkfungsi dipanggil, semua langkah sebelumnya telah terjadi - mengikat, transklusi, dll.

Ini biasanya merupakan tempat untuk memanipulasi DOM yang diberikan lebih lanjut.

Melakukan:

  • Memanipulasi elemen DOM (dirender, dan karenanya dipakai).
  • Lampirkan penangan acara.
  • Periksa elemen anak.
  • Atur pengamatan pada atribut.
  • Atur jam tangan pada ruang lingkup.
Izhaki
sumber
9
Seandainya ada orang yang menggunakan fungsi tautan (tanpa pra-tautan atau pasca-tautan), ada baiknya mengetahui bahwa itu setara dengan pasca-tautan.
Asaf David
15

Fungsi pra-tautan

pre-linkFungsi masing-masing direktif dipanggil setiap kali elemen terkait baru dipakai.

Seperti yang terlihat sebelumnya di bagian urutan kompilasi, pre-linkfungsi disebut parent-then-child, sedangkan post-linkfungsi dipanggil child-then-parent.

The pre-linkFungsi jarang digunakan, namun dapat berguna dalam skenario khusus; misalnya, ketika seorang anak kontroler mendaftarkan dirinya dengan pengendali orangtua, tetapi pendaftaran harus dengan parent-then-childcara ( ngModelControllermelakukan hal-hal seperti ini).

Tidak:

  • Periksa elemen anak (mereka mungkin belum diberikan, terikat pada ruang lingkup, dll.).
Izhaki
sumber