Lors de l'écriture d'une directive Angular, vous pouvez utiliser l'une des fonctions suivantes pour manipuler le comportement, le contenu et l'apparence du DOM de l'élément sur lequel la directive est déclarée:
Il semble y avoir une certaine confusion quant à la fonction à utiliser. Cette question couvre:
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
.
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 fonctionlink
appelée avant que leurs directives enfantscompile
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 .
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:
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).
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:
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:
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>'
}
});
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).
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.
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.
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).
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:
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>
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.
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
}
};
});
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
},
};
});
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
},
};
});
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:
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.
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:
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
.).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.
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:
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.
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.
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).