Wenn Sie eine Angular -Direktive schreiben, können Sie eine der folgenden Funktionen verwenden, um das DOM-Verhalten, den Inhalt und das Aussehen des Elements zu ändern, für das die Direktive deklariert ist:
Es scheint einige Verwirrung darüber zu geben, welche Funktion verwendet werden soll. Diese Frage umfasst:
Berücksichtigen Sie basierend auf dem folgenden plunk das folgende HTML-Markup:
<body>
<div log='some-div'></div>
</body>
Mit folgender Richtliniendeklaration:
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)' );
}
};
}
};
});
Die Konsolenausgabe lautet:
some-div (compile)
some-div (controller)
some-div (pre-link)
some-div (post-link)
Wir können sehen, dass compile
zuerst ausgeführt wird, dann controller
, dann pre-link
und zuletzt post-link
.
Hinweis: Das Folgende gilt nicht für Anweisungen, die ihre untergeordneten Elemente in ihrer Verknüpfungsfunktion rendern. Einige Angular Direktiven tun dies (wie ngIf, ngRepeat oder jede Direktive mit
transclude
). Diese Direktiven haben von Haus aus die Funktionlink
, die aufgerufen wird , bevor ihre untergeordneten Direktivencompile
aufgerufen werden.
Das ursprüngliche HTML-Markup besteht häufig aus verschachtelten Elementen mit jeweils eigenen Anweisungen. Wie im folgenden Markup (siehe plunk ):
<body>
<div log='parent'>
<div log='..first-child'></div>
<div log='..second-child'></div>
</div>
</body>
Die Konsolenausgabe sieht folgendermaßen aus:
// 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)
Wir können hier zwei Phasen unterscheiden - die Kompilierungsphase und die Verknüpfungsphase .
Wenn das DOM geladen ist, startet Angular die Kompilierungsphase, in der es das Markup von oben nach unten durchläuft und in allen Anweisungen compile
aufruft. Grafisch könnten wir es so ausdrücken:
Es ist vielleicht wichtig zu erwähnen, dass in diesem Stadium die Vorlagen, die die Kompilierungsfunktion erhält, die Quellvorlagen sind (keine Instanzvorlagen).
DOM-Instanzen sind oft einfach das Ergebnis einer Quellvorlage, die im DOM gerendert wird. Sie können jedoch von ng-repeat
erstellt oder im laufenden Betrieb eingeführt werden.
Immer wenn eine neue Instanz eines Elements mit einer Direktive in das DOM gerendert wird, beginnt die Verbindungsphase.
In dieser Phase ruft Angular controller
, pre-link
auf, iteriert untergeordnete Anweisungen und ruft post-link
bei allen Anweisungen auf.
Die verschiedenen Direktivenfunktionen werden innerhalb von zwei anderen angular Funktionen ausgeführt, die $compile
heißen (wobei die compile
der Direktive ausgeführt wird), und einer internen Funktion, die nodeLinkFn
heißt (wobei die Direktive heißt) controller
, preLink
und postLink
werden ausgeführt). Innerhalb der angular -Funktion passieren verschiedene Dinge, bevor und nachdem die Direktivenfunktionen aufgerufen werden. Am bemerkenswertesten ist vielleicht die Rekursion von Kindern. Die folgende vereinfachte Abbildung zeigt die wichtigsten Schritte in der Kompilierungs- und Verknüpfungsphase:
Verwenden Sie zur Veranschaulichung dieser Schritte das folgende HTML-Markup:
<div ng-repeat="i in [0,1,2]">
<my-element>
<div>Inner content</div>
</my-element>
</div>
Mit folgender Anweisung:
myApp.directive( 'myElement', function() {
return {
restrict: 'EA',
transclude: true,
template: '<div>{{label}}<div ng-transclude></div></div>'
}
});
Die compile
-API sieht folgendermaßen aus:
compile: function compile( tElement, tAttributes ) { ... }
Oft wird den Parametern t
vorangestellt, um zu kennzeichnen, dass die bereitgestellten Elemente und Attribute die der Quellvorlage und nicht die der Instanz sind.
Vor dem Aufruf von compile
wird der übertragene Inhalt (falls vorhanden) entfernt und die Vorlage auf das Markup angewendet. Daher sieht das Element, das für die Funktion compile
bereitgestellt wird, folgendermaßen aus:
<my-element>
<div>
"{{label}}"
<div ng-transclude></div>
</div>
</my-element>
Beachten Sie, dass der übertragene Inhalt an dieser Stelle nicht erneut eingefügt wird.
Nach dem Aufruf des .compile
der Direktive durchläuft Angular alle untergeordneten Elemente, einschließlich derjenigen, die gerade von der Direktive eingeführt wurden (z. B. die Template-Elemente).
In unserem Fall werden drei Instanzen der obigen Quellvorlage erstellt (von ng-repeat
). Daher wird die folgende Sequenz dreimal ausgeführt, einmal pro Instanz.
Die controller
API beinhaltet:
controller: function( $scope, $element, $attrs, $transclude ) { ... }
Beim Eintritt in die Verbindungsphase wird die über $compile
zurückgegebene Verbindungsfunktion nun mit einem Gültigkeitsbereich versehen.
Zunächst erstellt die Verknüpfungsfunktion auf Anforderung einen untergeordneten Bereich (scope: true
) oder einen isolierten Bereich (scope: {...}
).
Die Steuerung wird dann ausgeführt und mit dem Gültigkeitsbereich des Instanzelements versehen.
Die pre-link
-API sieht folgendermaßen aus:
function preLink( scope, element, attributes, controller ) { ... }
Zwischen dem Aufruf der Direktive .controller
und der Funktion .preLink
passiert praktisch nichts. Angular gibt weiterhin Empfehlungen zur Verwendung der einzelnen Optionen.
Nach dem Aufruf von .preLink
durchläuft die Verknüpfungsfunktion jedes untergeordnete Element. Dabei wird die richtige Verknüpfungsfunktion aufgerufen und der aktuelle Bereich (der als übergeordneter Bereich für untergeordnete Elemente dient) angehängt.
Die post-link
-API ähnelt der pre-link
-Funktion:
function postLink( scope, element, attributes, controller ) { ... }
Vielleicht ist es erwähnenswert, dass nach dem Aufrufen der Funktion .postLink
einer Anweisung der Verknüpfungsprozess aller untergeordneten Elemente abgeschlossen ist, einschließlich aller untergeordneten Funktionen .postLink
.
Dies bedeutet, dass die Kinder zum Zeitpunkt des Aufrufs von .postLink
"live" sind. Das beinhaltet:
Die Vorlage sieht zu diesem Zeitpunkt folgendermaßen aus:
<my-element>
<div class="ng-binding">
"{{label}}"
<div ng-transclude>
<div class="ng-scope">Inner content</div>
</div>
</div>
</my-element>
Wenn alle vier Funktionen verwendet werden sollen, folgt die Direktive dieser Form:
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
}
};
}
};
});
Beachten Sie, dass compile ein Objekt zurückgibt, das sowohl die Pre-Link- als auch die Post-Link-Funktion enthält. in Angular lingo sagen wir, dass die Kompilierungsfunktion ein Template-Funktion zurückgibt.
Wenn pre-link
nicht erforderlich ist, kann die Kompilierungsfunktion einfach die Nachverknüpfungsfunktion anstelle eines Definitionsobjekts zurückgeben, wie folgt:
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
};
}
};
});
Manchmal möchte man eine compile
-Methode hinzufügen, nachdem die (post) link
-Methode definiert wurde. Dafür kann man verwenden:
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
}
};
});
Wenn keine Kompilierungsfunktion benötigt wird, kann die Deklaration übersprungen und die Nachverknüpfungsfunktion unter der Eigenschaft link
des Konfigurationsobjekts der Direktive bereitgestellt werden:
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
},
};
});
In jedem der obigen Beispiele kann die Funktion controller
einfach entfernt werden, wenn sie nicht benötigt wird. Wenn beispielsweise nur die Funktion post-link
benötigt wird, kann Folgendes verwendet werden:
myApp.directive( 'myDirective', function () {
return {
restrict: 'EA',
link: function postLink( scope, element, attributes, controller, transcludeFn ) {
// Post-link code goes here
},
};
});
Die Tatsache, dass Angular die DOM-Manipulation ermöglicht, bedeutet, dass die Eingabemarkierung im Kompilierungsprozess manchmal von der Ausgabe abweicht. Insbesondere können einige Eingabemarkups einige Male geklont werden (z. B. mit ng-repeat
), bevor sie in das DOM gerendert werden.
Die Angular-Terminologie ist ein bisschen inkonsistent, unterscheidet jedoch immer noch zwischen zwei Arten von Markups:
Das folgende Markup zeigt dies:
<div ng-repeat="i in [0,1,2]">
<my-directive>{{i}}</my-directive>
</div>
Das Quell-HTML definiert
<my-directive>{{i}}</my-directive>
welches als Quellvorlage dient.
Da es sich jedoch um eine Anweisung ng-repeat
handelt, wird diese Quellvorlage geklont (in unserem Fall dreimal). Diese Klone sind Instanzvorlagen, die jeweils im DOM angezeigt werden und an den entsprechenden Bereich gebunden sind.
Die compile
-Funktion jeder Direktive wird nur einmal aufgerufen, wenn Angular bootstraps.
Offiziell ist dies der Ort, an dem (Quell-) Vorlagenmanipulationen durchgeführt werden können, bei denen weder Umfang noch Datenbindung erforderlich sind.
Dies geschieht in erster Linie zu Optimierungszwecken. Betrachten Sie das folgende Markup:
<tr ng-repeat="raw in raws">
<my-raw></my-raw>
</tr>
Die Direktive <my-raw>
gibt einen bestimmten Satz von DOM-Markups wieder. Also können wir entweder:
ng-repeat
, die Quellvorlage (<my-raw>
) zu duplizieren, und ändern Sie dann das Markup jeder Instanzvorlage (außerhalb der Funktion compile
).compile
), und erlauben Sie dann ng-repeat
, es zu duplizieren.Wenn die Sammlung raws
1000 Elemente enthält, ist die letztere Option möglicherweise schneller als die vorherige.
Die controller
-Funktion jeder Direktive wird immer dann aufgerufen, wenn ein neues verwandtes Element instanziiert wird.
Offiziell ist die Funktion controller
die folgende:
Auch hier ist zu beachten, dass, wenn die Direktive einen isolierten Bereich umfasst, alle darin enthaltenen Eigenschaften, die vom übergeordneten Bereich erben, noch nicht verfügbar sind.
Wenn die Funktion post-link
aufgerufen wird, haben alle vorherigen Schritte stattgefunden - Bindung, Transklusion usw.
Dies ist normalerweise ein Ort, an dem das gerenderte DOM weiter bearbeitet werden kann.
Die pre-link
-Funktion jeder Direktive wird immer dann aufgerufen, wenn ein neues verwandtes Element instanziiert wird.
Wie bereits im Abschnitt zur Kompilierungsreihenfolge erwähnt, werden pre-link
-Funktionen als Parent-Then-Child-Funktionen bezeichnet, während post-link
-Funktionen als child-then-parent
-Funktionen bezeichnet werden.
Die Funktion pre-link
wird selten verwendet, kann jedoch in bestimmten Szenarien hilfreich sein. Wenn sich beispielsweise ein untergeordneter Controller beim übergeordneten Controller registriert, die Registrierung jedoch auf parent-then-child
Weise erfolgen muss (ngModelController
erledigt dies auf diese Weise).