web-dev-qa-db-fra.com

Directive isoler la portée avec la portée ng-repeat dans AngularJS

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:

  • Comment puis-je prévenir 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?
  • Pourquoi le niveau de priorité est-il 2000 dans ma directive ne fonctionne pas?
  • À l'aide de Batarang, est-il possible de savoir quel type de scope est utilisé?
95
Deepak Nulu

Bon, à travers beaucoup des commentaires ci-dessus, j'ai découvert la confusion. Premièrement, quelques points de clarification:

  • ngRepeat n'affecte pas votre portée isolée
  • les paramètres passés à ngRepeat pour être utilisés sur les attributs de votre directive do utilisent une portée héritée du prototype
  • la raison pour laquelle votre directive ne fonctionne pas n'a rien à voir avec la portée isolée

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.

enter image description here

Après avoir cliqué sur le premier élément, les étendues se présentent comme suit:

enter image description here

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:

enter image description here


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:

enter image description here

Après avoir cliqué sur le premier élément:

enter image description here

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:

enter image description here

Scopes après avoir cliqué sur le premier élément:

enter image description here

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 ..

203
Josh David Miller

Sans essayer directement d'éviter de répondre à vos questions, jetez plutôt un coup d'œil au violon suivant:

http://jsfiddle.net/dVPLM/

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.
  • La priorité dans les directives ne se limite pas à l'ordre d'exécution - voir: AngularJS: Comment le compilateur HTML organise-t-il l'ordre de compilation?
  • Dans Batarang, si vous consultez l'onglet Performances, vous pouvez voir les expressions liées à chaque étendue et vérifier si cela correspond à vos attentes.
6
Alex Osborn