J'ai une directive avec un isolate-scope (pour pouvoir la réutiliser ailleurs) et lorsque j'utilise cette directive avec un ng-repeat
, ça ne marche pas.
J'ai lu toute la documentation et les réponses à Stack Overflow sur ce sujet et je comprends les problèmes. Je crois avoir évité tous les pièges habituels.
Je comprends donc que mon code échoue à cause de la portée créée par le ng-repeat
directive. Ma propre directive crée un isolate-scope et établit une liaison de données bidirectionnelle avec un objet dans l'étendue parent. Ma directive assignera une nouvelle valeur d'objet à cette variable liée et cela fonctionnera parfaitement lorsque ma directive sera utilisée sans ng-repeat
(la variable parent est mise à jour correctement). Cependant, avec ng-repeat
, l'affectation crée une nouvelle variable dans le ng-repeat
portée et la variable parent ne voit pas le changement. Tout cela est comme prévu sur la base de 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 qu’un 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é, puis leurs fonctions de compilation sont appelées (recherchez la priorité de Word à partir de http://docs.angularjs.org/guide/directive ).
J'espérais donc pouvoir utiliser la priorité pour m'assurer que ma directive est exécutée en premier et finit par créer un isolate-scope, et quand ng-repeat
s'exécute, il réutilise la portée d'isolat au lieu de créer une portée qui hérite de manière prototypique de la portée parente. Le ng-repeat
La documentation indique que cette directive s’exécute au niveau de priorité 1000
. Il n'est pas clair si 1
est un niveau de priorité supérieur ou inférieur. Quand j'ai utilisé le niveau de priorité 1
dans ma directive, cela n’a pas changé, j’ai donc essayé 2000
. Mais cela aggrave les choses: mes liaisons bidirectionnelles deviennent undefined
et ma directive n’affiche rien.
J'ai créé n violon pour montrer mon problème . J'ai commenté le paramètre priority
dans ma directive. J'ai une liste d'objets de nom et une directive appelée name-row
qui affiche les champs de prénom et de nom de famille dans l’objet de nom. Quand un nom affiché est cliqué, je veux qu'il définisse une variable selected
dans la portée principale. Le tableau de noms, la variable selected
est passé à la name-row
directive utilisant la 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 selected
se trouve dans un autre objet et que je me lie à l'objet extérieur, les choses fonctionneraient. Mais ces solutions ne m'intéressent pas pour le moment.
Au lieu de cela, les questions que j'ai sont les suivantes:
ng-repeat
de créer une étendue qui hérite de manière prototypique de la portée parent et qu’elle utilise plutôt la valeur isolate de ma directive?2000
dans ma directive ne fonctionne pas?Bon, à travers beaucoup des commentaires ci-dessus, j'ai découvert la confusion. Premièrement, quelques points de clarification:
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 champs d’application dans AngularJS utilisent un héritage prototypique. La valeur selected
de votre portée parent est une primitive . En JavaScript, cela signifie qu'il sera écrasé lorsqu'un enfant définira la même valeur. Il existe une règle d’or dans les champs d’application AngularJS: les valeurs du modèle doivent toujours avoir un .
en eux. C'est-à-dire qu'ils ne devraient jamais être des primitifs. Voir this SO answer pour plus d'informations.
Voici une image de ce à quoi les scopes ressemblent initialement.
Après avoir cliqué sur le premier élément, les étendues se présentent comme suit:
Notez qu'une nouvelle propriété selected
a été créée sur la portée de 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 en fait pas du tout causé par ngRepeat - il est causé par la violation d'une règle d'or dans AngularJS. La solution 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 isolée (car, encore une fois, cela n’a rien à voir avec votre problème), voici un JSFiddle , la vue doit également refléter L'object. Vous noterez que le seul changement nécessaire consistait à utiliser un objet au lieu d'un primitive.
Portées initialement:
Scopes après avoir cliqué sur le premier élément:
Pour conclure: encore une fois, votre problème ne concerne pas la portée isolate ni le fonctionnement de ngRepeat. Votre problème est que vous enfreignez une règle connue pour conduire à ce problème même. Les modèles dans AngularJS doivent toujours avoir un .
.
Sans essayer directement d'éviter de répondre à vos questions, jetez plutôt un coup d'œil au violon suivant:
Le point clé est qu'au lieu d'essayer de combattre et de changer le comportement conventionnel d'Angular, vous pouvez structurer votre directive pour qu'elle fonctionne avec ng-repeat
au lieu d'essayer de le remplacer.
Dans votre modèle:
<name-row
in-names-list="names"
io-selected="selected">
</name-row>
Dans votre directive:
template:
' <ul>' +
' <li ng-repeat="name in inNamesList" ng-class="activeClass($index)" >' +
' <a ng-click="setSelected($index)">' +
' {{$index}} - {{name.first}} {{name.last}}' +
' </a>' +
' </li>' +
' </ul>'
En réponse à vos questions:
ng-repeat
va créer une portée, vous ne devriez vraiment pas essayer de changer cela.