Direttive angolari - quando e come utilizzare compilazione, controller, pre-link e post-link [chiuso]


451

Quando si scrive una direttiva angolare, è possibile utilizzare una delle seguenti funzioni per manipolare il comportamento, i contenuti e l'aspetto dell'elemento DOM su cui viene dichiarata la direttiva:

  • compilare
  • controllore
  • pre-link
  • post-Link

Sembra esserci un po 'di confusione su quale funzione si dovrebbe usare. Questa domanda riguarda:

Nozioni di base sulla direttiva

Funzione natura, do e non

Domande correlate:


27
Che cosa?
haimlit

2
@Ian See: sovraccarico operatore . Fondamentalmente questo è destinato al wiki della comunità. Troppe risposte alle domande correlate sono parziali e non forniscono il quadro completo.
Izhaki,

8
Questo è un ottimo contenuto, ma chiediamo che tutto qui sia conservato nel formato Domande e risposte. Forse ti piacerebbe dividere questo in più domande discrete e quindi collegarti a loro dal tag wiki?
Flexo

57
Anche se questo post è fuori tema e in forma di blog, è stato molto utile nel fornire una spiegazione approfondita delle direttive angolari. Per favore non cancellare questo post, amministratori!
Esegesi

12
Onestamente, non mi preoccupo nemmeno dei documenti originali. Un post di StackOverflow o un blog di solito mi fa andare avanti in pochi secondi, contro i 15-30 minuti di strapparmi i capelli cercando di capire i documenti originali.
David

Risposte:


168

In quale ordine vengono eseguite le funzioni di direttiva?

Per una sola direttiva

Sulla base del seguente diagramma , considerare il seguente markup HTML:

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

Con la seguente dichiarazione direttiva:

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)'  );
                }
            };
         }
     };  

});

L'output della console sarà:

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

Possiamo vedere che compileviene eseguito per primo, quindi controller, quindi pre-linke ultimo è post-link.

Per direttive nidificate

Nota: quanto segue non si applica alle direttive che rendono i loro figli nella loro funzione di collegamento. Molte direttive angolari lo fanno (come ngIf, ngRepeat o qualsiasi direttiva con transclude). Queste direttive avranno la loro linkfunzione nativa chiamata prima che vengano chiamate le loro direttive figlio compile.

Il markup HTML originale è spesso composto da elementi nidificati, ognuno con una propria direttiva. Come nel seguente markup (vedi plunk ):

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

L'output della console sarà simile al seguente:

// 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)

Qui possiamo distinguere due fasi: la fase di compilazione e la fase di collegamento .

La fase di compilazione

Quando il DOM viene caricato, Angular avvia la fase di compilazione, dove attraversa il markup dall'alto verso il basso e richiama compiletutte le direttive. Graficamente, potremmo esprimerlo in questo modo:

Un'immagine che illustra il ciclo di compilazione per i bambini

È forse importante ricordare che in questa fase, i modelli che ottiene la funzione di compilazione sono i modelli di origine (non i modelli di istanza).

La fase di collegamento

Le istanze DOM sono spesso semplicemente il risultato del rendering di un modello di origine sul DOM, ma possono essere create ng-repeato introdotte al volo.

Ogni volta che una nuova istanza di un elemento con una direttiva viene trasmessa al DOM, inizia la fase di collegamento.

In questa fase, Angular chiama controller, pre-linkitera i bambini e fa appello post-linka tutte le direttive, in questo modo:

Un'illustrazione che dimostra i passaggi della fase di collegamento


5
@lzhaki Il diagramma di flusso sembra carino. Ti va di condividere il nome dello strumento per la creazione di grafici? :)
merlin,

1
@merlin Ho usato OmniGraffle (ma avrei potuto usare Illustrator o Inkscape - a parte la velocità, OmniGraffle non fa nulla di meglio di altri strumenti grafici per quanto riguarda questa illustrazione).
Izhaki,

2
Il plunker di Anant è scomparso, quindi eccone uno nuovo: plnkr.co/edit/kZZks8HN0iFIY8ZaKJkA?p=preview Apri la console JS per vedere le dichiarazioni del registro

PERCHÉ questo non è vero quando si usa ng-repeat per le direttive dei bambini ??? Vedi plunk: plnkr.co/edit/HcH4r6GV5jAFC3yOZknc?p=preview
Luckylooke

@Luckylooke Il tuo plunk non ha figli con direttiva sotto ng-repeat (cioè, ciò che viene ripetuto è un modello con una direttiva. Se così fosse, vedresti che la loro compilazione viene chiamata solo dopo il link di ng-repeat.
Izhaki il

90

Cos'altro succede tra queste chiamate di funzione?

Le varie funzioni direttive vengono eseguite dall'interno di altri due funzioni angolari chiamate $compile(quando la direttiva del compileviene eseguita) e una funzione interna chiamata nodeLinkFn(dove della direttiva controller, preLinke postLinkvengono eseguiti). Varie cose accadono all'interno della funzione angolare prima e dopo che le funzioni direttive sono chiamate. Forse in particolare è la ricorsione del bambino. La seguente illustrazione semplificata mostra i passaggi chiave nelle fasi di compilazione e collegamento:

Un'illustrazione che mostra le fasi di compilazione e collegamento angolari

Per dimostrare questi passaggi, utilizziamo il seguente markup HTML:

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

Con la seguente direttiva:

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

Compilare

L' compileAPI si presenta così:

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

Spesso ai parametri viene aggiunto il prefisso tper indicare che gli elementi e gli attributi forniti sono quelli del modello di origine, piuttosto che quello dell'istanza.

Prima della chiamata al compilecontenuto escluso (se presente) viene rimosso e il modello viene applicato al markup. Pertanto, l'elemento fornito alla compilefunzione sarà simile al seguente:

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

Si noti che il contenuto escluso non viene reinserito a questo punto.

A seguito della chiamata alla direttiva .compile, Angular attraverserà tutti gli elementi figlio, compresi quelli che potrebbero essere stati appena introdotti dalla direttiva (gli elementi del modello, ad esempio).

Creazione dell'istanza

Nel nostro caso, verranno create tre istanze del modello di origine sopra (di ng-repeat). Pertanto, la seguente sequenza verrà eseguita tre volte, una volta per istanza.

controllore

L' controllerAPI prevede:

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

Entrando nella fase di collegamento, la funzione di collegamento restituita tramite $compileora è dotata di un ambito.

Innanzitutto, la funzione di collegamento crea un ambito figlio ( scope: true) o un ambito isolato ( scope: {...}) se richiesto.

Il controller viene quindi eseguito, fornito con l'ambito dell'elemento istanza.

Pre-link

L' pre-linkAPI si presenta così:

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

Praticamente non succede nulla tra la chiamata alla direttiva .controllere la .preLinkfunzione. Angular fornisce ancora raccomandazioni su come utilizzare ciascuna.

A seguito della .preLinkchiamata, la funzione di collegamento attraverserà ciascun elemento figlio, chiamando la funzione di collegamento corretta e collegando ad esso l'ambito corrente (che funge da ambito padre per gli elementi figlio).

Post-Link

L' post-linkAPI è simile a quella della pre-linkfunzione:

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

Forse vale la pena notare che una volta .postLinkchiamata la funzione di una direttiva , il processo di collegamento di tutti i suoi elementi figlio è stato completato, comprese tutte le .postLinkfunzioni dei bambini .

Ciò significa che quando .postLinkviene chiamato, i bambini sono "vivi" pronti. Ciò comprende:

  • associazione dati
  • trasclusione applicata
  • ambito allegato

Il modello in questa fase apparirà così:

<my-element>
    <div class="ng-binding">
        "{{label}}"
        <div ng-transclude>                
            <div class="ng-scope">Inner content</div>
        </div>
    </div>
</my-element>

3
Come hai creato questo disegno?
Royi Namir,

6
@RoyiNamir Omnigraffle.
Izhaki,

43

Come dichiarare le varie funzioni?

Compila, controller, pre-link e post-link

Se si desidera utilizzare tutte e quattro le funzioni, la direttiva seguirà questo modulo:

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
                }
            };
        }
    };  
});

Si noti che la compilazione restituisce un oggetto contenente entrambe le funzioni pre-link e post-link; nel gergo angolare diciamo che la funzione di compilazione restituisce una funzione template .

Compila, controller e post-link

Se pre-linknon è necessario, la funzione di compilazione può semplicemente restituire la funzione post-link anziché un oggetto di definizione, in questo modo:

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                 
            };
        }
    };  
});

A volte, si desidera aggiungere un compilemetodo, dopo aver linkdefinito il metodo (post) . Per questo, si può usare:

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 e post-link

Se non è necessaria alcuna funzione di compilazione, si può saltare del tutto la sua dichiarazione e fornire la funzione post-link sotto la linkproprietà dell'oggetto di configurazione della direttiva:

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                 
        },          
    };  
});

Nessun controller

In uno qualsiasi degli esempi sopra, si può semplicemente rimuovere la controllerfunzione se non necessario. Ad esempio, se post-linkè necessaria solo la funzione, è possibile utilizzare:

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

31

Qual è la differenza tra un modello di origine e un modello di istanza ?

Il fatto che Angular consenta la manipolazione del DOM significa che il markup dell'input nel processo di compilazione a volte differisce dall'output. In particolare, alcuni markup di input possono essere clonati alcune volte (come con ng-repeat) prima di essere trasmessi al DOM.

La terminologia angolare è un po 'incoerente, ma distingue ancora tra due tipi di markup:

  • Modello di origine : il markup da clonare, se necessario. Se clonato, questo markup non verrà renderizzato sul DOM.
  • Modello di istanza : il markup effettivo da sottoporre a rendering sul DOM. Se è coinvolta la clonazione, ogni istanza sarà un clone.

Il seguente markup lo dimostra:

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

L'html di origine definisce

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

che funge da modello di origine.

Ma poiché è racchiuso in una ng-repeatdirettiva, questo modello di origine verrà clonato (3 volte nel nostro caso). Questi cloni sono modelli di istanza, ognuno apparirà nel DOM e sarà legato all'ambito pertinente.


23

Funzione di compilazione

La compilefunzione di ogni direttiva viene chiamata una sola volta, quando i bootstrap angolari.

Ufficialmente, questo è il posto dove eseguire manipolazioni di modelli (di origine) che non implicano ambito o associazione di dati.

In primo luogo, questo viene fatto a fini di ottimizzazione; considerare il seguente markup:

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

La <my-raw>direttiva renderà un particolare set di markup DOM. Quindi possiamo o:

  • Consentire ng-repeatdi duplicare il modello di origine ( <my-raw>), quindi modificare il markup di ciascun modello di istanza (al di fuori della compilefunzione).
  • Modificare il modello di origine per coinvolgere il markup desiderato (nella compilefunzione), quindi consentire ng-repeatdi duplicarlo.

Se ci sono 1000 articoli nella rawscollezione, quest'ultima opzione potrebbe essere più veloce di quella precedente.

Fare:

  • Manipola il markup in modo che funga da modello per le istanze (cloni).

Non

  • Allega gestori di eventi.
  • Ispeziona gli elementi figlio.
  • Imposta osservazioni sugli attributi.
  • Impostare gli orologi sul campo di applicazione.

20

Funzione controller

La controllerfunzione di ogni direttiva viene chiamata ogni volta che viene istanziato un nuovo elemento correlato.

Ufficialmente, la controllerfunzione è dove:

  • Definisce la logica (metodi) del controller che può essere condivisa tra i controller.
  • Avvia le variabili dell'ambito.

Ancora una volta, è importante ricordare che se la direttiva prevede un ambito isolato, tutte le proprietà al suo interno che ereditano dall'ambito padre non sono ancora disponibili.

Fare:

  • Definire la logica del controller
  • Avvia variabili di ambito

Non:

  • Ispezionare gli elementi figlio (potrebbero non essere ancora renderizzati, associati all'ambito, ecc.).

Sono contento che tu abbia citato Controller all'interno della direttiva è un ottimo posto per inizializzare l'ambito. Ho avuto difficoltà a scoprirlo.
jsbisht,

1
Il controller NON "Avvia l'ambito", accede solo all'ambito già avviato indipendentemente da esso.
Dmitri Zaitsev,

@DmitriZaitsev buona attenzione ai dettagli. Ho modificato il testo.
Izhaki,

19

Funzione post-link

Quando post-linkviene chiamata la funzione, sono stati eseguiti tutti i passaggi precedenti: rilegatura, inclusione, ecc.

In genere questo è un posto per manipolare ulteriormente il DOM renderizzato.

Fare:

  • Manipola gli elementi DOM (renderizzati e quindi istanziati).
  • Allega gestori di eventi.
  • Ispeziona gli elementi figlio.
  • Imposta osservazioni sugli attributi.
  • Impostare gli orologi sul campo di applicazione.

9
Nel caso in cui qualcuno stia usando la funzione link (senza pre-link o post-link), è bene sapere che è equivalente al post-link.
Asaf David,

15

Funzione pre-link

Ogni direttiva pre-linkfunzione di viene chiamata ogni volta che viene istanziato un nuovo elemento correlato.

Come visto in precedenza nella sezione dell'ordine di compilazione, le pre-linkfunzioni sono chiamate parent-then-child, mentre le post-linkfunzioni sono chiamatechild-then-parent .

La pre-linkfunzione viene utilizzata raramente, ma può essere utile in scenari speciali; ad esempio, quando un controllore figlio si registra con il controllore principale, ma la registrazione deve essere in un parent-then-childmodo (ngModelController fa le cose in questo modo).

Non:

  • Ispezionare gli elementi figlio (potrebbero non essere ancora renderizzati, associati all'ambito, ecc.).
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.