web-dev-qa-db-fra.com

Angular directives - quand et comment utiliser la compilation, le contrôleur, le pré-lien et le post-lien

450
Izhaki

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

Pour une seule directive

Sur la base 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 compile est exécuté en premier, puis controller, puis pre-link et en dernier lieu 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 lien. Un certain nombre de directives Angular le font (comme ngIf, ngRepeat ou toute directive avec transclude). Ces directives auront nativement leur fonction link appelée avant que leurs directives enfants compile soit appelée.

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 ici deux phases: la phase de compilation et la phase de liaison .

La phase de compilation

Lorsque le DOM est chargé, Angular lance la phase de compilation, où il parcourt le balisage de haut en bas et appelle compile dans toutes les directives. Graphiquement, nous pourrions l'exprimer comme suit:

An image illustrating the compilation loop for children

Il est peut-être important de mentionner qu’à ce stade, les modèles fournis à la fonction de compilation sont les modèles source (et non les modèles d’instance).

La phase de liaison

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

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

Dans cette phase, Angular appelle controller, pre-link, répète les enfants et appelle post-link dans toutes les directives, comme suit:

An illustration demonstrating the link phase steps

167
Izhaki

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

Les diverses fonctions de directive sont exécutées à partir de deux autres angular fonctions appelées $compile (où la directive compile est exécutée) et d'une fonction interne appelée nodeLinkFn (où sont exécutées les règles controller, preLink et postLink). . Diverses choses se passent dans la fonction angular avant et après l'appel des fonctions de directive. Peut-être le plus notable est la récursivité des enfants. L'illustration simplifiée suivante montre les étapes clés des phases de compilation et de liaison:

An illustration showing Angular compile and link phases

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'API compile ressemble à ceci:

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

t est souvent préfixé aux paramètres pour indiquer que les éléments et les attributs fournis sont ceux du modèle source plutôt que ceux de l'instance.

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

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

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

Suite à l'appel de 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 sera exécutée trois fois, une fois par instance.

Manette

L'API controller implique:

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

En entrant dans la phase de liaison, la fonction de liaison renvoyée via $compile est maintenant fournie avec une portée.

Tout d’abord, la fonction link crée une portée enfant (scope: true) ou 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'API pre-link ressemble à ceci:

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

Pratiquement rien ne se passe entre l'appel de la commande .controller de la directive et la fonction .preLink. Angular continue de fournir des recommandations sur la manière dont chacun devrait être utilisé.

Après l'appel .preLink, la fonction de liaison parcourt chaque élément enfant - en appelant la fonction de liaison correcte et en lui associant la portée actuelle (qui sert de portée parente pour les éléments enfants).

Post-lien

L'API post-link est similaire à celle de la fonction pre-link:

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

Il convient peut-être de noter qu’une fois que la fonction .postLink d’une directive est appelée, le processus de liaison de tous ses éléments enfants est terminé, y compris toutes les fonctions .postLink des enfants.

Cela signifie qu'au moment où .postLink est appelé, les enfants sont "en direct" prêts. Ceci comprend:

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

Le gabarit à 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>
90
Izhaki

Comment déclarer les différentes fonctions?

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

Si l’une des quatre fonctions est utilisée, la directive suivra le formulaire suivant:

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 compile renvoie un objet contenant à la fois les fonctions pré-lien et post-lien; dans Angular jargon nous disons que la fonction de compilation retourne un fonction template.

Compiler, Contrôleur & Post-link

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

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 méthode compile après avoir défini la méthode (post) link. 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 & Post-link

Si aucune fonction de compilation n'est nécessaire, vous pouvez ignorer sa déclaration et fournir la fonction post-link sous la propriété link 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 n'importe lequel des exemples ci-dessus, vous pouvez simplement supprimer la fonction controller si vous n'en avez pas besoin. Ainsi, par exemple, si seule la fonction post-link est requise, on peut utiliser:

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

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

Le fait que Angular autorise la manipulation du 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 au DOM.

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

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

Le balisage suivant montre ceci:

<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 encapsulé dans une directive ng-repeat, ce modèle source sera cloné (3 fois dans notre cas). Ces clones sont des modèles d'instance, chacun apparaîtra dans le DOM et sera lié à la portée correspondante.

30
Izhaki

Fonction de compilation

La fonction compile de chaque directive n'est appelée qu'une fois, lorsque Angular bootstraps.

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

Ceci est principalement effectué à des fins d'optimisation; considérez le balisage suivant:

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

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

  • Autorisez ng-repeat à dupliquer le modèle source (<my-raw>), puis modifiez le balisage de chaque modèle d'instance (en dehors de la fonction compile.).
  • Modifiez le modèle source pour qu'il implique le balisage souhaité (dans la fonction compile), puis laissez ng-repeat le dupliquer.

Si la collection raws contient 1 000 éléments, la 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.
  • Inspecter les éléments enfants.
  • Mettre en place des observations sur les attributs.
  • Mettre en place des montres sur la portée.
23
Izhaki

Fonction du contrôleur

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

Officiellement, la fonction controller est la suivante:

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

Là encore, il est important de garder à l'esprit que si la directive implique une étendue isolée, les propriétés qu'elle hérite de l'étendue 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 ne peuvent pas encore être rendus, liés à la portée, etc.).
19
Izhaki

Fonction post-link

Lorsque la fonction post-link est appelée, toutes les étapes précédentes ont été effectuées - liaison, transclusion, etc.

C'est typiquement un endroit pour manipuler davantage le DOM rendu.

Faire:

  • Manipuler des éléments DOM (rendus, et donc instanciés).
  • Attachez des gestionnaires d'événements.
  • Inspecter les éléments enfants.
  • Mettre en place des observations sur les attributs.
  • Mettre en place des montres sur la portée.
19
Izhaki

Fonction de pré-liaison

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

Comme indiqué précédemment dans la section relative à l'ordre de compilation, les fonctions pre-link sont appelées parent-then-child, alors que les fonctions post-link s'appellent child-then-parent.

La fonction pre-link 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 de type parent-then-child (ngModelController fait les choses de cette façon).

Ne pas:

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