Directive isoler la portée avec la portée ng-repeat dans AngularJS
J'ai une directive avec une portée isolée (pour que je puisse réutiliser la directive dans d'autres endroits), et lorsque j'utilise cette directive avec un ng-repeat
, cela ne fonctionne pas.
J'ai lu toute la documentation et les réponses de Stack Overflow sur ce sujet et je comprends les problèmes. Je crois que j'ai évité tous les pièges habituels.
Je comprends donc que mon code échoue à cause de la portée créée par la ng-repeat
directive. Ma propre directive crée une portée isolate et effectue une liaison de données bidirectionnelle à un objet de la portée parent. Ma directive attribuera une nouvelle valeur d'objet à cette variable liée et cela fonctionne parfaitement lorsque ma directive est utilisée sans ng-repeat
(la variable parente est mise à jour correctement). Cependant, avec ng-repeat
, l'affectation crée une nouvelle variable dans la ng-repeat
portée et la variable parent ne voit pas le changement. Tout cela est comme prévu d'après ce que j'ai lu.
J'ai également lu que lorsqu'il y a plusieurs directives sur un élément donné, une seule portée est créée. Et que a priority
peut être défini dans chaque directive pour définir l'ordre dans lequel les directives sont appliquées; les directives sont triées par priorité et ensuite leurs fonctions de compilation sont appelées (recherchez le mot priority sur http://docs.angularjs.org/guide/directive ).
J'espérais donc pouvoir utiliser la priorité pour m'assurer que ma directive s'exécute en premier et finit par créer une portée isolate, et lorsqu'elle ng-repeat
s'exécute, elle réutilise la portée isolate au lieu de créer une portée qui hérite prototypiquement de la portée parent. La ng-repeat
documentation indique que cette directive s'exécute au niveau de priorité 1000
. Il n'est pas clair s'il 1
s'agit d'un niveau de priorité plus élevé ou d'un niveau de priorité inférieur. Lorsque j'ai utilisé le niveau de priorité 1
dans ma directive, cela ne faisait aucune différence, alors j'ai essayé 2000
. Mais cela ne fait qu'empirer les choses: mes liaisons bidirectionnelles deviennent undefined
et ma directive n'affiche rien.
J'ai créé un violon pour montrer mon problème . J'ai commenté le priority
cadre de ma directive. J'ai une liste d'objets de nom et une directive appelée name-row
qui montre les champs de nom et prénom dans l'objet de nom. Lorsqu'un nom affiché est cliqué, je veux qu'il définisse une selected
variable dans la portée principale. Le tableau de noms, la selected
variable sont passés à la name-row
directive en utilisant une liaison de données bidirectionnelle.
Je sais comment faire fonctionner cela en appelant des fonctions dans la portée principale. Je sais aussi que si se selected
trouve à l'intérieur d'un autre objet et que je me lie à l'objet extérieur, les choses fonctionneraient. Mais je ne suis pas intéressé par ces solutions pour le moment.
Au lieu de cela, les questions que j'ai sont:
- Comment puis-je empêcher
ng-repeat
de créer une étendue qui hérite de manière prototypique de l'étendue parente et lui faire utiliser à la place l'étendue isolate de ma directive? - Pourquoi le niveau de priorité
2000
dans ma directive ne fonctionne-t-il pas? - En utilisant Batarang, est-il possible de savoir quel type de lunette est utilisé?
D'accord, à travers beaucoup de commentaires ci-dessus, j'ai découvert la confusion. Tout d'abord, quelques points de clarification:
- ngRepeat n'affecte pas la portée d'isolat choisie
- les paramètres passés dans ngRepeat pour une utilisation sur les attributs de votre directive n'utilisent une portée prototypiquement héritée
- la raison pour laquelle votre directive ne fonctionne pas n'a rien à voir avec la portée d'isolat
Voici un exemple du même code mais avec la directive supprimée:
<li ng-repeat="name in names"
ng-class="{ active: $index == selected }"
ng-click="selected = $index">
{{$index}}: {{name.first}} {{name.last}}
</li>
Voici un JSFiddle démontrant que cela ne fonctionnera pas. Vous obtenez exactement les mêmes résultats que dans votre directive.
Pourquoi ça ne marche pas? Parce que les étendues dans AngularJS utilisent l'héritage prototypique. La valeur selected
de votre étendue parent est une primitive . En JavaScript, cela signifie qu'il sera écrasé lorsqu'un enfant définit la même valeur. Il existe une règle d'or dans les étendues AngularJS: les valeurs de modèle doivent toujours avoir un .
. Autrement dit, ils ne devraient jamais être des primitifs. Voir cette réponse SO pour plus d'informations.
Voici une image de ce à quoi ressemblent initialement les oscilloscopes.
Après avoir cliqué sur le premier élément, les étendues ressemblent maintenant à ceci:
Notez qu'une nouvelle selected
propriété a été créée sur l'étendue ngRepeat. La portée du contrôleur 003 n'a pas été modifiée.
Vous pouvez probablement deviner ce qui se passe lorsque nous cliquons sur le deuxième élément:
Donc, votre problème n'est pas du tout causé par ngRepeat - il est causé par une violation d'une règle d'or dans AngularJS. La façon de résoudre ce problème consiste simplement à utiliser une propriété d'objet:
$scope.state = { selected: undefined };
<li ng-repeat="name in names"
ng-class="{ active: $index == state.selected }"
ng-click="state.selected = $index">
{{$index}}: {{name.first}} {{name.last}}
</li>
Voici un deuxième JSFiddle montrant que cela fonctionne aussi.
Voici à quoi ressemblent les portées initialement:
Après avoir cliqué sur le premier élément:
Ici, la portée du contrôleur est affectée, comme souhaité.
Aussi, pour prouver que cela fonctionnera toujours avec votre directive avec une portée d'isolat (car, encore une fois, cela n'a rien à voir avec votre problème), voici un JSFiddle pour cela aussi, la vue doit refléter l'objet. Vous noterez que le seul changement nécessaire était d'utiliser un objet au lieu d'une primitive .
Portées initialement:
Scopes après avoir cliqué sur le premier élément:
Pour conclure: encore une fois, votre problème n'est pas avec la portée d'isolat et ce n'est pas avec le fonctionnement de ngRepeat. Votre problème est que vous enfreignez une règle connue pour mener à ce problème. Les modèles dans AngularJS doivent toujours avoir un fichier .
.