60 votes

Angular.js: à l'Aide d'une directive à l'intérieur d'une ng-repeat, et un mystérieux pouvoir de la portée '@'

Si vous préférez voir la question dans le code de travail, commencez ici: http://jsbin.com/ayigub/2/edit

Considérez ceci près l'équivalent de manières d'écrire un simple direcive:

app.directive("drinkLonghand", function() {
  return {
    scope: {},
    template: '<div>{{flavor}}</div>',
    link: function(scope, element, attrs) {
      scope.flavor = attrs.flavor;
    }
  };
});

app.directive("drinkShortcut", function() {
  return {
    scope: { flavor: '@'},
    template: '<div>{{flavor}}</div>'
  };
});

Lorsqu'il est utilisé par eux-mêmes, les deux directives de travail et se comportent de façon identique:

  <!-- This works -->
  <div drink-longhand flavor="strawberry"></div>
  <hr/>

  <!-- This works -->
  <div drink-shortcut flavor="blueberry"></div>
  <hr/>

Toutefois, lorsqu'il est utilisé à l'intérieur d'une ng-repeat, seul le raccourci version fonctionne:

  <!-- Using the shortcut inside a repeat also works -->
  <div ng-repeat="flav in ['cherry', 'grape']">
    <div drink-shortcut flavor="{{flav}}"></div>
  </div>
  <hr/>

  <!-- HOWEVER: using the longhand inside a repeat DOESN'T WORK -->      
  <div ng-repeat="flav in ['cherry', 'grape']">
    <div drink-longhand flavor="{{flav}}"></div>
  </div>

Mes questions sont les suivantes:

  1. Pourquoi la main une version de travail à l'intérieur d'une ng-repeat?
  2. Comment pourriez-vous faire la main version de travail à l'intérieur d'une ng-repeat?

101voto

Brandon Tilley Points 49142

En drinkLonghand, vous devez utiliser le code

scope.flavor = attrs.flavor;

Lors de la liaison phase interpolée attributs n'ont pas encore été évalué, de sorte que leurs valeurs sont undefined. (Ils travaillent à l'extérieur de l' ng-repeat parce que dans ces cas, vous n'utilisez pas la chaîne de l'interpolation, que vous soyez simplement de passage dans une chaîne ordinaire, par exemple, "fraise".) Ceci est mentionné dans les Directives du guide du développeur, avec une méthode sur Attributes qui n'est pas présent dans la documentation de l'API appelée $observe:

Utiliser $observe d'observer les changements de valeur des attributs qui contiennent de l'interpolation (par exemple, src="{{bar}}"). Ce n'est pas seulement très efficace mais elle est aussi la seule façon d'obtenir facilement la valeur réelle, car lors de la liaison de la phase de l'interpolation n'a pas encore été évalué et donc la valeur est en ce moment à l' undefined.

Donc, pour résoudre ce problème, votre drinkLonghand directive devrait ressembler à ceci:

app.directive("drinkLonghand", function() {
  return {
    template: '<div>{{flavor}}</div>',
    link: function(scope, element, attrs) {
      attrs.$observe('flavor', function(flavor) {
        scope.flavor = flavor;
      });
    }
  };
});

Cependant, le problème, c'est qu'il n'utilise pas un isolat portée; ainsi, la ligne

scope.flavor = flavor;

a le potentiel de remplacer un pré-existante de la variable dans le champ d'application nommée flavor. En ajoutant un espace isoler portée aussi ne fonctionne pas; c'est parce Angulaire tentatives pour interpoler la chaîne en fonction du champ de la directive, sur laquelle il n'y a pas d'attribut appelé flav. (Vous pouvez le tester en ajoutant scope.flav = 'test'; au-dessus de l'appel à l' attrs.$observe.)

Bien sûr, vous pouvez résoudre ce problème avec un isolat de définition de la portée comme

scope: { flav: '@flavor' }

ou par la création d'un non-isoler enfant

scope: true

ou en ne comptant pas sur un template avec {{flavor}} et au lieu de faire des manipulations du DOM, comme

attrs.$observe('flavor', function(flavor) {
  element.text(flavor);
});

mais cela va à l'encontre de l'objectif de l'exercice (par exemple, il serait plus facile d'utiliser l' drinkShortcut méthode). Donc, pour faire de cette directive de travail, nous allons sortir de l' $interpolate service pour faire de l'interpolation nous-mêmes de la directive $parent portée:

app.directive("drinkLonghand", function($interpolate) {
  return {
    scope: {},
    template: '<div>{{flavor}}</div>',
    link: function(scope, element, attrs) {
      // element.attr('flavor') == '{{flav}}'
      // `flav` is defined on `scope.$parent` from the ng-repeat
      var fn = $interpolate(element.attr('flavor'));
      scope.flavor = fn(scope.$parent);
    }
  };
});

Bien sûr, cela ne fonctionne que pour la valeur initiale de l' scope.$parent.flav; si la valeur est capable de changer, vous devez utiliser $watch et de réévaluer le résultat de l'interpolation de la fonction fn (je ne suis pas positif sur le dessus de ma tête comment vous savez quoi $watch; vous pourriez avoir à passer dans une fonction). scope: { flavor: '@' } est un raccourci pratique pour éviter d'avoir à gérer toute cette complexité.

[Mise à jour]

Pour répondre à la question dans les commentaires:

Comment est le raccourci de la méthode de résolution de ce problème en coulisses? Est-ce à l'aide de l' $interpoler le service que vous avez fait, ou est-il en train de faire quelque chose d'autre?

Je n'étais pas sûr à ce sujet, donc j'ai cherché dans la source. J'ai trouvé le suivant en compile.js:

forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) {
   var match = definiton.match(LOCAL_REGEXP) || [],
       attrName = match[2]|| scopeName,
       mode = match[1], // @, =, or &
       lastValue,
       parentGet, parentSet;

   switch (mode) {

     case '@': {
       attrs.$observe(attrName, function(value) {
         scope[scopeName] = value;
       });
       attrs.$$observers[attrName].$$scope = parentScope;
       break;
     }

Il semble donc que, attrs.$observe peut être dit en interne pour utiliser un champ d'application différent de celui en cours à la base de l'attribut d'observation sur (l'avant-dernière ligne, au-dessus de l' break). Il peut être tentant d'utiliser vous-même, gardez à l'esprit que tout ce qui a le double dollar- $$ préfixe doit être considéré comme privé Angulaire privée de l'API, et sont sujettes à modification sans avertissement (ne pas mentionner que vous obtenez ce pour libre de toute façon lors de l'utilisation de l' @ mode).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X