Directives angulaires - quand et comment utiliser la compilation, le contrôleur, le pré-lien et le post-lien [fermé]

451

Lors de l'écriture d'une directive angulaire, on peut utiliser l'une des fonctions suivantes pour manipuler le comportement DOM, le contenu et l'apparence de l'élément sur lequel la directive est déclarée:

  • compiler
  • manette
  • pré-lien
  • post-lien

Il semble y avoir une certaine confusion quant à la fonction à utiliser. Cette question couvre:

Bases de la directive

Nature de la fonction, choses à faire et à ne pas faire

Questions connexes:

Izhaki
la source
27
Qu'est-ce que quoi?
haimlit
2
@Ian Voir: surcharge de l'opérateur . Cela est essentiellement destiné au wiki communautaire. Trop de réponses aux questions connexes sont partielles et ne fournissent pas une image complète.
Izhaki
8
C'est un excellent contenu, mais nous demandons que tout ici soit conservé dans le format Q&R. Peut-être que vous souhaitez diviser cela en plusieurs questions distinctes, puis les lier à partir du wiki de balises?
Flexo
57
Même si ce post est hors sujet et sous forme de blog, il a été très utile pour fournir une explication approfondie des directives angulaires. Veuillez ne pas supprimer ce message, admins!
Exégèse du
12
Honnêtement, je ne me soucie même pas des documents originaux. Un post stackoverflow ou un blog me fait généralement avancer en quelques secondes, contre les 15-30 minutes de déchirure de mes cheveux en essayant de comprendre les documents originaux.
David

Réponses:

168

Dans quel ordre les fonctions directives sont-elles exécutées?

Pour une seule directive

En fonction du plunk suivant , considérez le balisage HTML suivant:

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

Avec la déclaration de directive suivante:

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

});

La sortie de la console sera:

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

Nous pouvons voir que cela compileest exécuté en premier, puis controller, puis en pre-linkdernier post-link.

Pour les directives imbriquées

Remarque: ce qui suit ne s'applique pas aux directives qui rendent leurs enfants dans leur fonction de liaison. Un bon nombre de directives angulaires le font (comme ngIf, ngRepeat ou toute directive avec transclude). Ces directives verront nativement leur linkfonction appelée avant l' appel de leurs directives enfants compile.

Le balisage HTML d'origine est souvent composé d'éléments imbriqués, chacun avec sa propre directive. Comme dans le balisage suivant (voir plunk ):

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

La sortie de la console ressemblera à ceci:

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

Nous pouvons distinguer deux phases ici - la phase de compilation et la phase de liaison .

La phase de compilation

Lorsque le DOM est chargé, Angular démarre la phase de compilation, où il parcourt le balisage de haut en bas et appelle compiletoutes les directives. Graphiquement, nous pourrions l'exprimer ainsi:

Une image illustrant la boucle de compilation pour les enfants

Il est peut-être important de mentionner qu'à ce stade, les modèles que la fonction de compilation obtient sont les modèles source (pas le modèle d'instance).

La phase de liaison

Les instances DOM sont souvent simplement le résultat d'un modèle source rendu au DOM, mais elles peuvent être créées par ng-repeatou introduites à la volée.

Chaque fois qu'une nouvelle instance d'un élément avec une directive est rendue au DOM, la phase de liaison démarre.

Dans cette phase, Angular appelle controller, pre-linkitère les enfants et appelle post-linktoutes les directives, comme ceci:

Une illustration illustrant les étapes de la phase de liaison

Izhaki
la source
5
@lzhaki L'organigramme est joli. Voulez-vous partager le nom de l'outil de création de graphiques? :)
merlin
1
@merlin J'ai utilisé OmniGraffle (mais j'aurais pu utiliser Illustrator ou Inkscape - à part la vitesse, il n'y a rien d'OmniGraffle qui fait mieux que d'autres outils graphiques en ce qui concerne cette illustration).
Izhaki
2
@ Le plunker d'Anant a disparu alors en voici un nouveau: plnkr.co/edit/kZZks8HN0iFIY8ZaKJkA?p=preview Ouvrez la console JS pour voir les instructions du journal
POURQUOI ce n'est pas vrai lorsque ng-repeat est utilisé pour les directives enfants ??? Voir plunk
Luckylooke
@Luckylooke Votre plunk n'a pas d'enfants avec directive sous ng-repeat (c'est-à-dire que ce qui est répété est un modèle avec une directive. Si c'est le cas, vous verriez que leur compilation n'est appelée qu'après le lien de ng-repeat.
Izhaki
90

Que se passe-t-il d'autre entre ces appels de fonction?

Les diverses fonctions de directive sont exécutées à partir de deux autres fonctions angulaires appelées $compile(où la directive compileest exécutée) et une fonction interne appelée nodeLinkFn(où celle de la directive controller, preLinket postLinksont exécutées). Diverses choses se produisent dans la fonction angulaire avant et après l'appel des fonctions directives. Peut-être plus particulièrement est la récursivité enfant. L'illustration simplifiée suivante montre les étapes clés des phases de compilation et de liaison:

Une illustration montrant les phases de compilation et de liaison angulaires

Pour illustrer ces étapes, utilisons le balisage HTML suivant:

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

Avec la directive suivante:

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

Compiler

L' compileAPI ressemble à ceci:

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

Souvent, les paramètres sont préfixés tpour signifier que les éléments et les attributs fournis sont ceux du modèle source, plutôt que ceux de l'instance.

Avant l'appel au compilecontenu transclus (le cas échéant) est supprimé et le modèle est appliqué au balisage. Ainsi, l'élément fourni à la compilefonction ressemblera à ceci:

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

Notez que le contenu transclus n'est pas réinséré à ce stade.

Après l'appel à la directive .compile, Angular traversera tous les éléments enfants, y compris ceux qui viennent d'être introduits par la directive (les éléments de modèle, par exemple).

Création d'instance

Dans notre cas, trois instances du modèle source ci-dessus seront créées (par ng-repeat). Ainsi, la séquence suivante s'exécutera trois fois, une fois par instance.

Manette

L' controllerAPI implique:

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

En entrant dans la phase de liaison, la fonction de liaison renvoyée via $compileest désormais dotée d'une étendue.

Tout d'abord, la fonction de liaison crée une portée enfant ( scope: true) ou une portée isolée ( scope: {...}) si elle est demandée.

Le contrôleur est ensuite exécuté, avec la portée de l'élément d'instance.

Pré-lien

L' pre-linkAPI ressemble à ceci:

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

Il ne se passe pratiquement rien entre l'appel à la directive .controlleret la .preLinkfonction. Angular fournit toujours des recommandations sur la façon dont chacun doit être utilisé.

Après l' .preLinkappel, la fonction de liaison traversera chaque élément enfant - en appelant la fonction de lien correcte et en y attachant la portée actuelle (qui sert de portée parent pour les éléments enfants).

Post-lien

L' post-linkAPI est similaire à celle de la pre-linkfonction:

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

Il convient peut-être de noter qu'une fois la .postLinkfonction d' une directive appelée, le processus de liaison de tous ses éléments enfants est terminé, y compris toutes les .postLinkfonctions enfants .

Cela signifie qu'au moment de l' .postLinkappel, les enfants sont «vivants» sont prêts. Ceci comprend:

  • liaison de données
  • transclusion appliquée
  • portée attachée

Le modèle à ce stade ressemblera donc à ceci:

<my-element>
    <div class="ng-binding">
        "{{label}}"
        <div ng-transclude>                
            <div class="ng-scope">Inner content</div>
        </div>
    </div>
</my-element>
Izhaki
la source
3
Comment avez-vous créé ce dessin?
Royi Namir
6
@RoyiNamir Omnigraffle.
Izhaki
43

Comment déclarer les différentes fonctions?

Compilation, contrôleur, pré-lien et post-lien

Si l'on doit utiliser les quatre fonctions, la directive suivra cette forme:

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

Notez que la compilation renvoie un objet contenant à la fois les fonctions pré-lien et post-lien; en jargon angulaire, nous disons que la fonction de compilation renvoie une fonction de modèle .

Compilation, contrôleur et post-lien

Si ce pre-linkn'est pas nécessaire, la fonction de compilation peut simplement renvoyer la fonction post-lien au lieu d'un objet de définition, comme ceci:

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

Parfois, on souhaite ajouter une compileméthode, une fois la linkméthode (post) définie. Pour cela, on peut utiliser:

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
        }

    };  
});

Contrôleur et post-lien

Si aucune fonction de compilation n'est nécessaire, on peut ignorer complètement sa déclaration et fournir la fonction post-lien sous la linkpropriété de l'objet de configuration de la directive:

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

Pas de contrôleur

Dans l'un des exemples ci-dessus, on peut simplement supprimer la controllerfonction si elle n'est pas nécessaire. Ainsi, par exemple, si seule la post-linkfonction est nécessaire, on peut utiliser:

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

Quelle est la différence entre un modèle source et un modèle d'instance ?

Le fait qu'Angular autorise la manipulation DOM signifie que le balisage d'entrée dans le processus de compilation diffère parfois de la sortie. En particulier, certaines balises d'entrée peuvent être clonées plusieurs fois (comme avec ng-repeat) avant d'être rendues dans le DOM.

La terminologie angulaire est un peu incohérente, mais elle distingue toujours deux types de balises:

  • Modèle source - le balisage à cloner, si nécessaire. S'il est cloné, ce balisage ne sera pas rendu dans le DOM.
  • Modèle d'instance - le balisage réel à rendre au DOM. Si le clonage est impliqué, chaque instance sera un clone.

Le balisage suivant illustre cela:

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

Le code source HTML définit

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

qui sert de modèle source.

Mais comme il est enveloppé dans une ng-repeatdirective, ce modèle source sera cloné (3 fois dans notre cas). Ces clones sont un modèle d'instance, chacun apparaîtra dans le DOM et sera lié à la portée appropriée.

Izhaki
la source
23

Fonction de compilation

La compilefonction de chaque directive n'est appelée qu'une seule fois, lors du démarrage angulaire.

Officiellement, c'est l'endroit pour effectuer des manipulations de modèle (source) qui n'impliquent pas la portée ou la liaison de données.

Cela se fait principalement à des fins d'optimisation; considérez le balisage suivant:

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

La <my-raw>directive rendra un ensemble particulier de balises DOM. On peut donc soit:

  • Autorisez la ng-repeatduplication du modèle source ( <my-raw>), puis modifiez le balisage de chaque modèle d'instance (en dehors de la compilefonction).
  • Modifiez le modèle source pour impliquer le balisage souhaité (dans la compilefonction), puis autorisez-le ng-repeatà le dupliquer.

S'il y a 1000 articles dans la rawscollection, cette dernière option peut être plus rapide que la précédente.

Faire:

  • Manipulez le balisage pour qu'il serve de modèle aux instances (clones).

Ne pas

  • Attachez des gestionnaires d'événements.
  • Inspectez les éléments enfants.
  • Configurez des observations sur les attributs.
  • Mettre en place des montres sur la lunette.
Izhaki
la source
20

Fonction contrôleur

La controllerfonction de chaque directive est appelée chaque fois qu'un nouvel élément associé est instancié.

Officiellement, la controllerfonction est là où l'on:

  • Définit la logique du contrôleur (méthodes) pouvant être partagée entre les contrôleurs.
  • Initie les variables de portée.

Encore une fois, il est important de se rappeler que si la directive implique une portée isolée, toutes les propriétés qu'elle hérite de la portée parent ne sont pas encore disponibles.

Faire:

  • Définir la logique du contrôleur
  • Initier des variables de portée

Ne pas:

  • Inspectez les éléments enfants (ils peuvent ne pas encore être rendus, liés à la portée, etc.).
Izhaki
la source
Heureux que vous ayez mentionné Controller dans la directive est un excellent endroit pour initialiser la portée. J'ai eu du mal à le découvrir.
jsbisht
1
Le contrôleur ne "lance pas la portée", il accède uniquement à la portée déjà initiée indépendamment de lui.
Dmitri Zaitsev
@DmitriZaitsev bonne attention aux détails. J'ai modifié le texte.
Izhaki
19

Fonction post-lien

Lorsque la post-linkfonction est appelée, toutes les étapes précédentes ont eu lieu - liaison, transclusion, etc.

C'est généralement un endroit pour manipuler davantage le DOM rendu.

Faire:

  • Manipuler les éléments DOM (rendus et donc instanciés).
  • Attachez des gestionnaires d'événements.
  • Inspectez les éléments enfants.
  • Configurez des observations sur les attributs.
  • Mettre en place des montres sur la lunette.
Izhaki
la source
9
Dans le cas où quelqu'un utilise la fonction de lien (sans pré-lien ou post-lien), il est bon de savoir qu'elle est équivalente au post-lien.
Asaf David
15

Fonction de pré-liaison

La pre-linkfonction de chaque directive est appelée chaque fois qu'un nouvel élément associé est instancié.

Comme vu précédemment dans la section Ordre de compilation, les pre-linkfonctions sont appelées parent-puis-enfant, tandis que les post-linkfonctions sont appelées child-then-parent.

La pre-linkfonction est rarement utilisée, mais peut être utile dans des scénarios spéciaux; par exemple, lorsqu'un contrôleur enfant s'enregistre auprès du contrôleur parent, mais que l'enregistrement doit être effectué de la même parent-then-childmanière ( ngModelControllerfait les choses de cette façon).

Ne pas:

  • Inspectez les éléments enfants (ils peuvent ne pas encore être rendus, liés à la portée, etc.).
Izhaki
la source