Quelle est la différence entre la fonction de compilation et de liaison dans angularjs

208

Quelqu'un peut-il expliquer en termes simples?

La documentation semble un peu obtuse. Je ne comprends pas l'essence et la vue d'ensemble de quand utiliser l'un sur l'autre. Un exemple contrastant les deux serait génial.

numan salati
la source
2
Peut-être un aperçu plus complet des fonctions de directive: directives angulaires - quand utiliser la compilation, le contrôleur, le pré-lien et le post-lien .
Izhaki
jvandemo.com/…
EMuentes

Réponses:

217
  • fonction de compilation - à utiliser pour la manipulation du modèle DOM (c'est-à-dire la manipulation de l'élément tElement = template), d'où les manipulations qui s'appliquent à tous les clones DOM du modèle associé à la directive.

  • fonction de liaison - à utiliser pour enregistrer les écouteurs DOM (c'est-à-dire les expressions $ watch sur la portée de l'instance) ainsi que la manipulation DOM d' instance (c'est-à-dire la manipulation de iElement = élément d'instance individuel).
    Il est exécuté après le clonage du modèle. Par exemple, à l'intérieur d'un <li ng-repeat ...>, la fonction de lien est exécutée après que le modèle <li> (tElement) a été cloné (dans un iElement) pour cet élément particulier <li>.
    Un $ watch () permet à une directive d'être notifiée des changements de propriété de portée d'instance (une portée d'instance est associée à chaque instance), ce qui permet à la directive de restituer une valeur d'instance mise à jour au DOM - en copiant le contenu de la portée d'instance dans le DOM.

Notez que les transformations DOM peuvent être effectuées dans la fonction de compilation et / ou la fonction de liaison.

La plupart des directives n'ont besoin que d'une fonction de lien, car la plupart des directives ne traitent qu'avec une instance d'élément DOM spécifique (et sa portée d'instance).

Une façon d'aider à déterminer lequel utiliser: considérez que la fonction de compilation ne reçoit pas d' scopeargument. (J'ignore délibérément l'argument de la fonction de liaison de transclude, qui reçoit une étendue transclue - ceci est rarement utilisé.) Ainsi, la fonction de compilation ne peut rien faire que vous voudriez faire qui nécessite une étendue (d'instance) - vous pouvez 't $ watch aucune propriété de portée de modèle / instance, vous ne pouvez pas manipuler le DOM en utilisant les informations de portée d'instance, vous ne pouvez pas appeler de fonctions définies sur la portée d'instance, etc.

Cependant, la fonction de compilation (comme la fonction de liaison) a accès aux attributs. Donc, si vos manipulations DOM ne nécessitent pas la portée de l'instance, vous pouvez utiliser une fonction de compilation. Voici un exemple de directive qui utilise uniquement une fonction de compilation, pour ces raisons. Il examine les attributs, mais il n'a pas besoin d'une étendue d'instance pour faire son travail.

Voici un exemple de directive qui utilise également uniquement une fonction de compilation. La directive n'a besoin que de transformer le modèle DOM, donc une fonction de compilation peut être utilisée.

Une autre façon d'aider à déterminer lequel utiliser: si vous n'utilisez pas le paramètre "element" dans la fonction de lien, alors vous n'avez probablement pas besoin d'une fonction de lien.

Étant donné que la plupart des directives ont une fonction de lien, je ne vais pas donner d'exemples - elles devraient être très faciles à trouver.

Notez que si vous avez besoin d'une fonction de compilation et d'une fonction de lien (ou de fonctions de lien avant et après), la fonction de compilation doit renvoyer la ou les fonctions de lien car l'attribut 'link' est ignoré si l'attribut 'compile' est défini.

Voir également

Mark Rajcok
la source
5
Meilleure explication sur la compilation vs le lien.
Nexus23
1
Quand vous dites if you don't use the "element" parameter in the link function, then you probably don't need a link function.que vous voulez dire "portée" au lieu de "élément"?
Jason Larke
69

J'ai frappé ma tête contre le mur pendant quelques jours, et je pense qu'un peu plus d'explications s'imposent.

Fondamentalement, les documents mentionnent que la séparation est en grande partie une amélioration des performances. Je réitère que la phase de compilation est principalement utilisée lorsque vous devez modifier le DOM AVANT que les sous-éléments eux-mêmes ne soient compilés.

Pour nos besoins, je vais insister sur la terminologie, qui est par ailleurs confuse:

Le compilateur SERVICE ($ compile) est le mécanisme angulaire qui traite le DOM et exécute les différents bits de code dans les directives.

La fonction de compilation est un bit de code dans une directive, qui est exécuté à un moment donné PAR le service de compilation ($ compile).

Quelques notes sur la fonction de compilation:

  1. Vous ne pouvez pas modifier l'élément ROOT (celui que votre directive affecte), car il est déjà en cours de compilation à partir du niveau externe de DOM (le service de compilation a déjà analysé les directives sur cet élément).

  2. Si vous souhaitez ajouter d'autres directives aux éléments (imbriqués), vous pouvez soit:

    1. Je dois les ajouter pendant la phase de compilation.

    2. Vous devez injecter le service de compilation dans la phase de liaison et compiler les éléments manuellement. MAIS, méfiez-vous de compiler quelque chose deux fois!

Il est également utile de voir comment l'imbrication et les appels explicites à $ compile fonctionnent, j'ai donc créé une aire de jeux pour la visualiser sur http://jsbin.com/imUPAMoV/1/edit . Fondamentalement, il enregistre simplement les étapes dans console.log.

Je vais exposer les résultats de ce que vous verriez dans ce bac ici. Pour un DOM de directives personnalisées tp et sp imbriquées comme suit:

<tp>
   <sp>
   </sp>
</tp>

Le service de compilation angulaire appellera:

tp compile
sp compile
tp pre-link
sp pre-link
sp post-link
tp post-link

Le code jsbin a également la fonction tp post-link FUNCTION appeler explicitement le service de compilation sur une troisième directive (up), qui effectue les trois étapes à la fin.

Maintenant, je veux parcourir quelques scénarios pour montrer comment on pourrait utiliser la compilation et le lien pour faire diverses choses:

SCÉNARIO 1: Directive en tant que MACRO

Vous voulez ajouter une directive (par exemple ng-show) dynamiquement à quelque chose dans votre modèle que vous pouvez dériver d'un attribut.

Supposons que vous ayez un templateUrl qui pointe vers:

<div><span><input type="text"></span><div>

et vous voulez une directive personnalisée:

<my-field model="state" name="address"></my-field>

qui transforme le DOM en ceci:

<div><span ng-show="state.visible.address"><input ng-model="state.fields.address" ...>

en gros, vous voulez réduire le nombre de points chauds en ayant une structure de modèle cohérente que votre directive peut interpréter. En d'autres termes: vous voulez une macro.

C'est une excellente utilisation pour la phase de compilation, car vous pouvez baser toutes les manipulations DOM sur des choses que vous connaissez uniquement à partir des attributs. Utilisez simplement jQuery pour ajouter les attributs:

compile: function(tele, tattr) {
   var span = jQuery(tele).find('span').first();
   span.attr('ng-show', tattr.model + ".visible." + tattr.name);
   ...
   return { 
     pre: function() { },
     post: function() {}
   };
}

La séquence des opérations sera (vous pouvez le voir via le jsbin mentionné précédemment):

  1. Le service de compilation trouve mon champ
  2. Il appelle la fonction de compilation sur la directive, qui met à jour le DOM.
  3. Le service de compilation marche ensuite dans le DOM résultant, et COMPILE (récursivement)
  4. Le service de compilation appelle ensuite la liaison descendante de haut en bas
  5. Le service de compilation appelle ensuite BOTTOM UP après la liaison, de sorte que la fonction de liaison de mon champ est appelée APRÈS que les nœuds intérieurs ont été liés.

Dans l'exemple ci-dessus, aucun lien n'est nécessaire, car tout le travail de la directive a été effectué dans la fonction de compilation.

À tout moment, le code d'une directive peut demander au service de compilation de s'exécuter sur des éléments supplémentaires.

Cela signifie que nous pouvons faire exactement la même chose dans une fonction de lien si vous injectez le service de compilation:

directive('d', function($compile) {
  return {
    // REMEMBER, link is called AFTER nested elements have been compiled and linked!
    link: function(scope, iele, iattr) {
      var span = jQuery(iele).find('span').first();
      span.attr('ng-show', iattr.model + ".visible." + iattr.name);
      // CAREFUL! If span had directives on it before
      // you will cause them to be processed again:
      $compile(span)(scope);
    }
});

Si vous êtes sûr que les éléments que vous transmettez à $ compile SERVICE étaient à l'origine sans directive (par exemple, ils proviennent d'un modèle que vous avez défini, ou vous les avez simplement créés avec angular.element ()), alors le résultat final est à peu près comme avant (bien que vous puissiez répéter certains travaux). Cependant, si l'élément comportait d'autres directives, vous venez de provoquer leur traitement à nouveau, ce qui peut provoquer toutes sortes de comportements erratiques (par exemple, double enregistrement des événements et des surveillances).

Ainsi, la phase de compilation est un bien meilleur choix pour le travail de style macro.

SCÉNARIO 2: configuration DOM via les données de portée

Celui-ci découle de l'exemple ci-dessus. Supposons que vous ayez besoin d'accéder à la portée lors de la manipulation du DOM. Eh bien, dans ce cas, la section de compilation est inutile pour vous, car elle se produit avant qu'une étendue soit disponible.

Supposons donc que vous souhaitiez supprimer une entrée avec des validations, mais que vous souhaitiez exporter vos validations à partir d'une classe ORM côté serveur (DRY), les appliquer automatiquement et générer l'interface utilisateur côté client appropriée pour ces validations.

Votre modèle pourrait pousser:

scope.metadata = {
  validations: {
     address: [ {
       pattern: '^[0-9]',
       message: "Address must begin with a number"
     },
     { maxlength: 100,
       message: "Address too long"
     } ]
  }
};
scope.state = {
  address: '123 Fern Dr'
};

et vous voudrez peut-être une directive:

<form name="theForm">
  <my-field model="state" metadata="metadata" name="address">
</form>

pour inclure automatiquement les directives et divs appropriés afin d'afficher les diverses erreurs de validation:

<form name="theForm">
  <div>
    <input ng-model="state.address" type="text">
    <div ng-show="theForm.address.$error.pattern">Address must begin with a number</input>
...

Dans ce cas, vous avez certainement besoin d'accéder à la portée (car c'est là que vos validations sont stockées), et vous devrez compiler les ajouts manuellement, en faisant également attention à ne pas double-compiler les choses. (en guise de remarque, vous devez définir un nom sur la balise de formulaire contenant (je suppose que le formulaire est ici) et y accéder en lien avec iElement.parent (). controller ('form'). $ name) .

Dans ce cas, il est inutile d'écrire une fonction de compilation. Le lien est vraiment ce que vous voulez. Les étapes seraient les suivantes:

  1. Définissez un modèle complètement dépourvu de directives angulaires.
  2. Définissez une fonction de lien qui ajoute les différents attributs
  3. SUPPRIMER toutes les directives angulaires que vous pourriez autoriser sur votre élément de niveau supérieur (la directive my-field). Ils ont déjà été traités et c'est un moyen de les empêcher d'être doublés.
  4. Terminez en appelant le service de compilation sur votre élément de niveau supérieur

Ainsi:

angular.module('app', []).
directive('my-field', function($compile) {
  return {
    link: function(scope, iele, iattr) {
      // jquery additions via attr()
      // remove ng attr from top-level iele (to avoid duplicate processing)
      $compile(iele)(scope); // will pick up additions
    }
  };
});

Vous pouvez, bien sûr, compiler les éléments imbriqués un par un pour éviter d'avoir à vous soucier du traitement en double des directives ng lorsque vous compilez à nouveau l'élément de niveau supérieur.

Une dernière remarque sur ce scénario: j'ai laissé entendre que vous pousseriez la définition des validations à partir d'un serveur, et dans mon exemple, je les ai montrées comme des données déjà dans la portée. Je laisse comme un exercice pour le lecteur de comprendre comment on pourrait gérer la nécessité d'extraire ces données d'une API REST (indice: compilation différée).

SCÉNARIO 3: liaison de données bidirectionnelle via lien

Bien sûr, l'utilisation la plus courante du lien consiste à simplement connecter la liaison de données bidirectionnelle via watch / apply. La plupart des directives entrent dans cette catégorie et sont donc couvertes de manière adéquate ailleurs.

Tony K.
la source
2
Réponse géniale et cool!
Nexus23
Comment ajouter des éléments imbriqués sans les compiler deux fois?
Art713
50

De la documentation:

Compilateur

Le compilateur est un service angulaire qui parcourt le DOM à la recherche d'attributs. Le processus de compilation se déroule en deux phases.

  1. Compiler: parcourir le DOM et collecter toutes les directives. Le résultat est une fonction de liaison.

  2. Lien: combinez les directives avec un champ d'application et produisez une vue en direct. Toutes les modifications du modèle d'étendue sont reflétées dans la vue et toutes les interactions de l'utilisateur avec la vue sont reflétées dans le modèle d'étendue. Faire du modèle de portée une source unique de vérité.

Certaines directives ng-repeatclonent ces éléments DOM une fois pour chaque élément de la collection. Une phase de compilation et de liaison améliore les performances, car le modèle cloné ne doit être compilé qu'une seule fois, puis lié une fois pour chaque instance de clone.

Ainsi, au moins dans certains cas, les deux phases existent séparément en tant qu'optimisation.


De @ UmurKontacı :

Si vous allez faire des transformations DOM, ça devrait l'être compile. Si vous souhaitez ajouter des fonctionnalités qui modifient le comportement, elles doivent l'être link.

Matt Ball
la source
46
Si vous souhaitez effectuer une DOMtransformation, ce devrait être le compilecas si vous souhaitez ajouter des fonctionnalités à des changements de comportement, cela devrait être le cas link.
Umur Kontacı
4
+1 au commentaire ci-dessus; c'est la description la plus concise que j'ai trouvée jusqu'à présent. Cela correspond au tutoriel que j'ai trouvé ici .
Benny Bottema
18

Ceci est tiré du discours de Misko sur les directives. http://youtu.be/WqmeI5fZcho?t=16m23s

Considérez la fonction de compilation comme la chose qui fonctionne sur un modèle et la chose qui est autorisée à changer le modèle lui-même en y ajoutant, par exemple, une classe ou quelque chose du genre. Mais c'est la fonction de liaison qui fait réellement le travail de liaison entre les deux parce que la fonction de liaison a accès à la portée et c'est la fonction de liaison qui s'exécute une fois pour chaque instanciation du modèle particulier. Ainsi, le seul type de choses que vous pouvez placer à l'intérieur des fonctions de compilation sont des choses communes à toutes les instances.

SunnyShah
la source
10

Un peu tard pour le fil. Mais, pour le bénéfice des futurs lecteurs:

Je suis tombé sur la vidéo suivante qui explique la compilation et la liaison dans Angular JS de manière très intéressante:

https://www.youtube.com/watch?v=bjFqSyddCeA

Il ne serait pas agréable de copier / saisir tout le contenu ici. J'ai pris quelques captures d'écran de la vidéo, qui expliquent chaque étape des phases de compilation et de liaison:

Compiler et lier dans JS angulaire

Compiler et lier dans JS angulaire - Directives imbriquées

La deuxième capture d'écran est un peu déroutante. Mais, si nous suivons la numérotation des étapes, c'est assez simple.

Premier cycle: "Compiler" est exécuté en premier sur toutes les directives.
Deuxième cycle: "Controller" et "Pre-Link" sont exécutés (juste l'un après l'autre) Troisième cycle: "Post-Link" est exécuté dans l'ordre inverse (à partir de l'intérieur)

Voici le code qui illustre ce qui précède:

var app = angular.module ('app', []);

app.controller ('msg', ['$ scope', function ($ scope) {

}]);

app.directive ('message', fonction ($ interpoler) {
    revenir{

        compile: fonction (tElement, tAttributes) { 
            console.log (tAttributes.text + "-In compile ..");
            revenir {

                pre: fonction (portée, iElement, iAttributes, contrôleur) {
                    console.log (iAttributes.text + "-In pre ..");
                },

                post: fonction (portée, iElement, iAttributes, contrôleur) {
                    console.log (iAttributes.text + "-In Post ..");
                }

            }
        },

        contrôleur: fonction ($ scope, $ element, $ attrs) {
            console.log ($ attrs.text + "-In controller ..");
        },

    }
});
<body ng-app="app">
<div ng-controller="msg">
    <div message text="first">
        <div message text="..second">
            <div message text="....third">

            </div>              
        </div>  
    </div>
</div>

METTRE À JOUR:

La partie 2 de la même vidéo est disponible ici: https://www.youtube.com/watch?v=1M3LZ1cu7rw La vidéo explique plus comment modifier DOM et gérer les événements pendant le processus de compilation et de liaison d'Angular JS, dans un exemple simple .

user203687
la source
Utilisé compileet postpour modifier un DOM avant qu'il ne soit modifié en templatepartie à partir d'une directive fournisseur.
jedi
6

Deux phases: compiler et lier

Compiler:

Parcourez l'arborescence DOM à la recherche de directives (éléments / attributs / classes / commentaires). Chaque compilation d'une directive peut modifier son modèle, ou modifier son contenu qui n'a pas encore été compilé. Une fois qu'une directive est mise en correspondance, elle renvoie une fonction de liaison, qui est utilisée dans une phase ultérieure pour lier des éléments entre eux. À la fin de la phase de compilation, nous avons une liste des directives compilées et leurs fonctions de liaison correspondantes.

Lien:

Lorsqu'un élément est lié, l'arborescence DOM est rompue à son point de branche dans l'arborescence DOM et le contenu est remplacé par l'instance compilée (et liée) du modèle. Le contenu déplacé d'origine est soit rejeté, soit en cas de transclusion, re-lié dans le modèle. Avec la transclusion, les deux pièces sont reliées ensemble (un peu comme une chaîne, la pièce modèle étant au milieu). Lorsque la fonction de liaison est appelée, le modèle a déjà été lié à une étendue et ajouté en tant qu'enfant de l'élément. La fonction de lien est l'occasion de manipuler davantage le DOM et de configurer des écouteurs de changement.

pixelbits
la source
3

Cette question est ancienne par je voudrais faire un bref résumé qui peut aider:

  • Compilation appelée une fois pour toutes les instances de directive
  • Le but principal de la compilation est de retourner / créer le lien (et éventuellement le pré / post) fonction / objet. Vous pouvez également initier des éléments partagés entre des instances de la directive.
  • À mon avis, "lien" est un nom déroutant pour cette fonctionnalité. Je préférerais "pré-rendre".
  • un lien est appelé pour chaque instance de directive et son but est de préparer le rendu de la directive dans le DOM.
Kfir Erez
la source
1
un plus pour le nom de la suggestion: "pré-rendu"
Hailong Cao