Winkelanweisungen - wann und wie Kompilierung, Controller, Pre-Link und Post-Link verwendet werden [geschlossen]

451

Beim Schreiben einer Angular-Direktive 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:

  • kompilieren
  • Regler
  • Pre-Link
  • Post-Link

Es scheint einige Verwirrung darüber zu geben, welche Funktion man verwenden soll. Diese Frage umfasst:

Grundlagen der Richtlinie

Funktion Natur, tun und nicht tun

Verwandte Fragen:

Izhaki
quelle
27
Was zum was?
Haimlit
2
@ Ian Siehe: Überlastung des Bedieners . Im Wesentlichen ist dies für das Community-Wiki gedacht. Zu viele der Antworten auf die damit verbundenen Fragen sind teilweise und liefern nicht das vollständige Bild.
Izhaki
8
Das ist großartiger Inhalt, aber wir bitten darum, dass hier alles im Q & A-Format bleibt. Vielleicht möchten Sie dies in mehrere diskrete Fragen aufteilen und dann aus dem Tag-Wiki auf diese verlinken?
Flexo
57
Obwohl dieser Beitrag nicht zum Thema gehört und in Blog-Form vorliegt, war er am nützlichsten, um Angular-Anweisungen ausführlich zu erläutern. Bitte löschen Sie diesen Beitrag nicht, Admins!
Exegese
12
Ehrlich gesagt kümmere ich mich nicht einmal um die Originaldokumente. Ein Stackoverflow-Beitrag oder ein Blog bringt mich normalerweise innerhalb von Sekunden zum Laufen, im Gegensatz zu den 15 bis 30 Minuten, in denen ich mir die Haare reiße und versuche, die Originaldokumente zu verstehen.
David

Antworten:

168

In welcher Reihenfolge werden die Direktivenfunktionen ausgeführt?

Für eine einzige Richtlinie

Betrachten Sie basierend auf dem folgenden Plunk das folgende HTML-Markup:

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

Mit folgender Richtlinienerklärung:

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 compiledas zuerst ausgeführt wird, dann controller, dann pre-linkund zuletzt post-link.

Für verschachtelte Anweisungen

Hinweis: Das Folgende gilt nicht für Anweisungen, die ihre untergeordneten Elemente in ihrer Verknüpfungsfunktion rendern. Nicht wenige Angular-Direktiven tun dies (wie ngIf, ngRepeat oder eine Direktive mit transclude). Diese Anweisungen haben ihre linkFunktion nativ aufgerufen, bevor ihre untergeordneten Anweisungen compileaufgerufen werden.

Das ursprüngliche HTML-Markup besteht häufig aus verschachtelten Elementen mit jeweils einer eigenen Direktive. 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 .

Die Kompilierungsphase

Wenn das DOM geladen ist, startet Angular die Kompilierungsphase, in der es das Markup von oben nach unten durchläuft und compilealle Anweisungen aufruft . Grafisch könnten wir es so ausdrücken:

Ein Bild, das die Kompilierungsschleife für Kinder darstellt

Es ist vielleicht wichtig zu erwähnen, dass zu diesem Zeitpunkt die Vorlagen, die die Kompilierungsfunktion erhält, die Quellvorlagen sind (keine Instanzvorlage).

Die Verbindungsphase

DOM-Instanzen sind häufig einfach das Ergebnis einer Quellvorlage, die in das DOM gerendert wird. Sie können jedoch von erstellt ng-repeatoder im laufenden Betrieb eingeführt werden.

Immer wenn eine neue Instanz eines Elements mit einer Direktive in das DOM gerendert wird, beginnt die Verknüpfungsphase.

In dieser Phase Angular Anrufe controller, pre-link, iteriert Kinder und Anruf post-linkauf allen Richtlinien, etwa so:

Eine Illustration, die die Verbindungsphasenschritte demonstriert

Izhaki
quelle
5
@lzhaki Das Flussdiagramm sieht gut aus. Möchten Sie den Namen des Diagrammwerkzeugs mitteilen? :)
Merlin
1
@merlin Ich habe OmniGraffle verwendet (hätte aber auch Illustrator oder Tintenlandschaft verwenden können - außer Geschwindigkeit gibt es nichts, was OmniGraffle in Bezug auf diese Abbildung besser kann als andere Diagrammwerkzeuge).
Izhaki
2
@ Anants Plunker ist verschwunden, daher hier ein neuer: plnkr.co/edit/kZZks8HN0iFIY8ZaKJkA?p=preview Öffnen Sie die JS-Konsole, um die Protokollanweisungen
WARUM ist das nicht wahr, wenn ng-repeat für Kinderanweisungen verwendet wird ??? Siehe plunk: plnkr.co/edit/HcH4r6GV5jAFC3yOZknc?p=preview
Luckylooke
@Luckylooke Ihr Plunk hat keine Kinder mit Direktive unter ng-repeat (dh was wiederholt wird, ist eine Vorlage mit einer Direktive. Wenn dies der Fall wäre, würden Sie sehen, dass ihre Kompilierung nur nach dem Link von ng-repeat aufgerufen wird.
Izhaki
90

Was passiert sonst noch zwischen diesen Funktionsaufrufen?

Die verschiedenen Direktivenfunktionen werden innerhalb von zwei anderen Winkelfunktionen ausgeführt, die aufgerufen werden $compile(wo die Direktiven compileausgeführt werden), und einer internen Funktion, die aufgerufen wird nodeLinkFn(wo die Direktiven ausgeführt werden controller, preLinkund postLinkausgeführt werden). Innerhalb der Winkelfunktion passieren verschiedene Dinge, bevor und nachdem die Direktivenfunktionen aufgerufen werden. Am bemerkenswertesten ist vielleicht die Kinderrekursion. Die folgende vereinfachte Abbildung zeigt die wichtigsten Schritte in der Kompilierungs- und Verknüpfungsphase:

Eine Abbildung zeigt Angular-Kompilierungs- und Verknüpfungsphasen

Um diese Schritte zu demonstrieren, verwenden wir das folgende HTML-Markup:

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

Mit folgender Richtlinie:

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

Kompilieren

Die compileAPI sieht folgendermaßen aus:

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

Oft wird den Parametern ein Präfix vorangestellt, um tzu kennzeichnen, dass die bereitgestellten Elemente und Attribute eher die der Quellvorlage als die der Instanz sind.

Vor dem Aufruf von compiletranskludiertem Inhalt (falls vorhanden) wird entfernt und die Vorlage auf das Markup angewendet. Das für die compileFunktion bereitgestellte Element sieht also folgendermaßen aus:

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

Beachten Sie, dass der übertragene Inhalt zu diesem Zeitpunkt nicht erneut eingefügt wird.

Nach dem Aufruf der Direktive .compiledurchläuft Angular alle untergeordneten Elemente, einschließlich derjenigen, die möglicherweise gerade von der Direktive eingeführt wurden (z. B. die Vorlagenelemente).

Instanzerstellung

In unserem Fall werden drei Instanzen der obigen Quellvorlage (von ng-repeat) erstellt. Daher wird die folgende Sequenz dreimal pro Instanz ausgeführt.

Regler

Die controllerAPI beinhaltet:

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

In der Verbindungsphase wird die über zurückgegebene Verbindungsfunktion $compilenun mit einem Bereich versehen.

Zunächst erstellt die Verknüpfungsfunktion auf Anfrage einen untergeordneten Bereich ( scope: true) oder einen isolierten Bereich ( scope: {...}).

Der Controller wird dann ausgeführt und mit dem Bereich des Instanzelements versehen.

Pre-Link

Die pre-linkAPI sieht folgendermaßen aus:

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

Zwischen dem Aufruf der Direktive .controllerund der .preLinkFunktion passiert praktisch nichts . Angular gibt weiterhin Empfehlungen, wie jedes verwendet werden sollte.

Nach dem .preLinkAufruf 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.

Post-Link

Die post-linkAPI ähnelt der der pre-linkFunktion:

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

Es ist vielleicht erwähnenswert, dass nach dem .postLinkAufruf der Funktion einer Direktive der Verknüpfungsprozess aller untergeordneten Elemente abgeschlossen ist, einschließlich aller untergeordneten .postLinkFunktionen.

Dies bedeutet, dass zum Zeitpunkt des .postLinkAnrufs die Kinder "leben" und bereit sind. Das beinhaltet:

  • Datenbindung
  • Transklusion angewendet
  • Umfang beigefügt

Die Vorlage in dieser Phase sieht also folgendermaßen aus:

<my-element>
    <div class="ng-binding">
        "{{label}}"
        <div ng-transclude>                
            <div class="ng-scope">Inner content</div>
        </div>
    </div>
</my-element>
Izhaki
quelle
3
Wie haben Sie diese Zeichnung erstellt?
Royi Namir
6
@ RoyiNamir Omnigraffle.
Izhaki
43

Wie deklariere ich die verschiedenen Funktionen?

Kompilieren, Steuern, Pre-Link & Post-Link

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 beim Kompilieren ein Objekt zurückgegeben wird, das sowohl die Pre-Link- als auch die Post-Link-Funktion enthält. Im Angular-Jargon sagen wir, dass die Kompilierungsfunktion eine Vorlagenfunktion zurückgibt .

Kompilieren, Controller & Post-Link

Wenn dies pre-linknicht erforderlich ist, kann die Kompilierungsfunktion einfach die Post-Link-Funktion 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 compileMethode hinzufügen , nachdem die (Post-) linkMethode definiert wurde. Hierfü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
        }

    };  
});

Controller & Post-Link

Wenn keine Kompilierungsfunktion benötigt wird, kann man die Deklaration insgesamt überspringen und die Post-Link-Funktion unter der linkEigenschaft des Konfigurationsobjekts der Direktive bereitstellen :

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

Kein Controller

In jedem der obigen Beispiele kann die controllerFunktion einfach entfernt werden, wenn sie nicht benötigt wird. Wenn zum Beispiel nur eine post-linkFunktion benötigt wird, kann man Folgendes verwenden:

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

Was ist der Unterschied zwischen einer Quellvorlage und einer Instanzvorlage ?

Die Tatsache, dass Angular die DOM-Manipulation ermöglicht, bedeutet, dass sich das Eingabe-Markup für den Kompilierungsprozess manchmal von der Ausgabe unterscheidet. Insbesondere können einige Eingabemarkups einige Male (wie bei ng-repeat) geklont werden, bevor sie in das DOM gerendert werden.

Die Winkelterminologie ist etwas inkonsistent, unterscheidet jedoch zwei Arten von Markups:

  • Quellvorlage - das Markup, das bei Bedarf geklont werden soll. Wenn geklont, wird dieses Markup nicht im DOM gerendert.
  • Instanzvorlage - das tatsächliche Markup, das im DOM gerendert werden soll. Wenn das Klonen beteiligt ist, ist jede Instanz ein Klon.

Das folgende Markup zeigt dies:

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

Die Quell-HTML definiert

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

welches als Quellvorlage dient.

Da diese Quellvorlage jedoch in eine ng-repeatDirektive eingeschlossen ist, wird sie geklont (in unserem Fall dreimal). Diese Klone sind Instanzvorlagen, die jeweils im DOM angezeigt werden und an den entsprechenden Bereich gebunden sind.

Izhaki
quelle
23

Kompilierungsfunktion

Die compileFunktion 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 erfolgt hauptsächlich zu Optimierungszwecken. Beachten Sie das folgende Markup:

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

Die <my-raw>Direktive rendert einen bestimmten Satz von DOM-Markups. Also können wir entweder:

  • Ermöglichen Sie ng-repeatdas Duplizieren der Quellvorlage ( <my-raw>) und ändern Sie dann das Markup jeder Instanzvorlage (außerhalb der compileFunktion).
  • Ändern Sie die Quellvorlage so, dass sie das gewünschte Markup (in der compileFunktion) enthält, und lassen Sie ng-repeates dann duplizieren.

Wenn die rawsSammlung 1000 Elemente enthält , ist die letztere Option möglicherweise schneller als die erstere.

Tun:

  • Bearbeiten Sie das Markup so, dass es als Vorlage für Instanzen (Klone) dient.

Unterlassen Sie

  • Fügen Sie Ereignishandler hinzu.
  • Untersuchen Sie untergeordnete Elemente.
  • Richten Sie Beobachtungen zu Attributen ein.
  • Stellen Sie Uhren am Zielfernrohr auf.
Izhaki
quelle
20

Controller-Funktion

Die controllerFunktion jeder Direktive wird immer dann aufgerufen, wenn ein neues verwandtes Element instanziiert wird.

Offiziell ist die controllerFunktion, wo man:

  • Definiert Controller-Logik (Methoden), die von Controllern gemeinsam genutzt werden können.
  • Initiiert Bereichsvariablen.

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.

Tun:

  • Steuerungslogik definieren
  • Initiieren Sie Bereichsvariablen

Unterlassen Sie:

  • Überprüfen Sie untergeordnete Elemente (sie werden möglicherweise noch nicht gerendert, sind an den Umfang gebunden usw.).
Izhaki
quelle
Ich bin froh, dass Sie Controller in der Richtlinie erwähnt haben. Dies ist ein großartiger Ort, um den Geltungsbereich zu initialisieren. Es fiel mir schwer, das zu entdecken.
Jsbisht
1
Der Controller "initiiert den Bereich nicht", sondern greift nur unabhängig davon auf den bereits initiierten Bereich zu.
Dmitri Zaitsev
@DmitriZaitsev viel Liebe zum Detail. Ich habe den Text geändert.
Izhaki
19

Post-Link-Funktion

Wenn die post-linkFunktion aufgerufen wird, haben alle vorherigen Schritte stattgefunden - Bindung, Transklusion usw.

Dies ist normalerweise ein Ort, an dem das gerenderte DOM weiter bearbeitet werden kann.

Tun:

  • Bearbeiten Sie DOM-Elemente (gerendert und damit instanziiert).
  • Fügen Sie Ereignishandler hinzu.
  • Untersuchen Sie untergeordnete Elemente.
  • Richten Sie Beobachtungen zu Attributen ein.
  • Stellen Sie Uhren am Zielfernrohr auf.
Izhaki
quelle
9
Wenn jemand die Link-Funktion verwendet (ohne Pre-Link oder Post-Link), ist es gut zu wissen, dass sie dem Post-Link entspricht.
Asaf David
15

Pre-Link-Funktion

Die pre-linkFunktion jeder Direktive wird immer dann aufgerufen, wenn ein neues verwandtes Element instanziiert wird.

Wie bereits im Abschnitt zur Kompilierungsreihenfolge erwähnt, werden pre-linkFunktionen als Eltern-dann-Kind bezeichnet, während post-linkFunktionen aufgerufen werden child-then-parent.

Die pre-linkFunktion wird selten verwendet, kann jedoch in speziellen Szenarien hilfreich sein. Zum Beispiel, wenn sich ein untergeordneter Controller beim übergeordneten Controller registriert, die Registrierung jedoch auf eine Art und parent-then-childWeise erfolgen muss (dies ngModelControllergeschieht auf diese Weise).

Unterlassen Sie:

  • Überprüfen Sie untergeordnete Elemente (sie werden möglicherweise noch nicht gerendert, sind an den Umfang gebunden usw.).
Izhaki
quelle