Comment parcourir les éléments retournés par une fonction avec ng-repeat?

114

Je veux créer des divs à plusieurs reprises, les éléments sont des objets retournés par une fonction. Cependant, le code suivant signale des erreurs: 10 itérations $ digest () atteintes. Abandon! jsfiddle est ici: http://jsfiddle.net/BraveOstrich/awnqm/

<body ng-app>
  <div ng-repeat="entity in getEntities()">
    Hello {{entity.id}}!
  </div>
</body>
Xiao Peng - ZenUML.com
la source

Réponses:

195

Réponse courte : avez-vous vraiment besoin d'une telle fonction ou pouvez-vous utiliser la propriété? http://jsfiddle.net/awnqm/1/

Longue réponse

Pour simplifier, je ne décrirai que votre cas - ngRepeat pour un tableau d'objets. Aussi, je vais omettre certains détails.

AngularJS utilise la vérification sale pour détecter les changements. Lorsque l' application est lancée , elle fonctionne $digestpour $rootScope. $digesteffectuera une traversée en profondeur pour la hiérarchie de la portée . Toutes les lunettes ont une liste de montres. Chaque montre a la dernière valeur (initialement initWatchVal). Pour chaque portée de toutes les montres $digest, il l'exécute, obtient la valeur actuelle ( watch.get(scope)) et la compare à watch.last. Si la valeur actuelle n'est pas égale à watch.last(toujours pour la première comparaison), la valeur est $digestdéfinie dirtysur true. Lorsque toutes les étendues sont traitées, si dirty == true $digestcommence une autre traversée en profondeur à partir de $rootScope. $digestse termine quand dirty == false ou nombre de traversées == 10. Dans ce dernier cas, l'erreur "10 $ digest () iterations atteints." sera enregistré.

Maintenant environ ngRepeat. Pour chaque watch.getappel, il stocke les objets de la collection (valeur de retour de getEntities) avec des informations supplémentaires dans le cache ( HashQueueMappar hashKey). Pour chaque watch.getappel ngRepeatessaie d'obtenir un objet par son hashKeydu cache. Si elle n'existe pas dans le cache, ngRepeatil stocke dans le cache, crée de nouvelles possibilités, met objet sur elle, crée élément DOM, etc .

Maintenant environ hashKey. Il hashKeys'agit généralement d' un numéro unique généré par nextUid(). Mais cela peut être une fonction . hashKeyest stocké dans l'objet après la génération pour une utilisation future.

Pourquoi votre exemple génère une erreur : la fonction getEntities()renvoie toujours un tableau avec un nouvel objet. Cet objet n'a pas hashKeyet n'existe pas dans le ngRepeatcache. Ainsi ngRepeat, chacun watch.getgénère une nouvelle portée pour cela avec une nouvelle montre pour {{entity.id}}. Cette montre le premier watch.geta watch.last == initWatchVal. Alors watch.get() != watch.last. Alors $digestcommence une nouvelle traversée. Crée donc une ngRepeatnouvelle portée avec une nouvelle montre. Donc ... après 10 traversées, vous obtenez une erreur.

Comment vous pouvez y remédier

  1. Ne créez pas de nouveaux objets à chaque getEntities()appel.
  2. Si vous avez besoin de créer de nouveaux objets, vous pouvez leur ajouter une hashKeyméthode. Consultez cette rubrique pour des exemples.

J'espère que les gens qui connaissent les composants internes d'AngularJS me corrigeront si je me trompe dans quelque chose.

Artem Andreev
la source
4
+1 merci pour cela. J'ai eu le même problème et je ne pouvais pas utiliser une propriété statique pour cela. $$ hashKey devrait vraiment être documenté sur la page ngRepeat du manuel IMO.
Michael Moussa
Une idée de ce qui a changé de 1.1.3 à 1.1.4 qui a affecté cela? Avant la version 1.1.4, cela fonctionnait réellement. Il n'y a rien dans le changelog à ce sujet et je ne peux pas comprendre quelle est la différence. Le comportement actuel a du sens.
m59
Vérifiez également ceci si vous le pouvez: stackoverflow.com/questions/20933261/ ... Je ne sais pas si ma réponse est la voie à suivre ou pas ..
m59
2
Donc, suivre la recommandation Do not create new objects on every getEntities() call.peut être corrigé assez facilement comme ceci:<div ng-repeat="entity in entities = (entities || getEntities())">
przno
2
la solution de mon commentaire précédent fonctionne dans le cas où quand getEntities()retourne toujours le même tableau, si le tableau change un jour, vous ne l'obtiendrez pas dans leng-repeat
przno
44

Initialiser le tableau en dehors de la répétition

<body ng-app>
   <div ng-init="entities = getEntities()">
       <div ng-repeat="entity in entities">
           Hello {{entity.id}}!
       </div>
   </div>
</body>
Mwayi
la source
8
Cela ne fonctionne pas si le getEntities()renvoie quelque chose de différent dans le cycle de vie du programme. Disons, par exemple, que cela getEntities()déclenche un $http.get. Lorsque le get enfin résoudre (vous avez effectué l'appel AJAX), entitiessera déjà initialisé.
Nighto
3
Tiré de la documentation angulaireThe only appropriate use of ngInit is for aliasing special properties of ngRepeat. Besides this case, you should use controllers rather than ngInit to initialize values on a scope.
Blowsie
Je pense que le point principal est de "initialiser le tableau en dehors de la répétition" par n'importe quel moyen ... et @Nighto bon appel sur les promesses
Mwayi
15

Cela a été signalé ici et a obtenu cette réponse:

Votre getter n'est pas idempotent et change le modèle (en générant un nouveau tableau à chaque fois qu'il est appelé). Cela oblige angular à continuer à l'appeler dans l'espoir que le modèle finira par se stabiliser, mais il ne le fait jamais, angular abandonne et jette une exception.

Les valeurs renvoyées par le getter sont égales mais pas identiques et c'est le problème.

Vous pouvez voir ce comportement disparaître si vous déplacez la baie hors du contrôleur principal:

var array = [{id:'angularjs'}];
function Main($scope) {
    $scope.getEntities = function(){return array;};
};

car maintenant il renvoie le même objet à chaque fois. Vous devrez peut-être réorganiser votre modèle pour utiliser une propriété sur la portée au lieu d'une fonction:

Nous avons contourné ce problème en attribuant le résultat de la méthode du contrôleur à une propriété et en effectuant ng: repeat contre elle.

Dennis
la source
L'utilisation d'une propriété peut être le seul moyen si la fonction a un paramètre qui change à chaque itération.
Stephane
7

Basé sur le commentaire de @przno

<body ng-app>
  <div ng-repeat="item in t = angular.equals(t, getEntities()) ? t : getEntities()">
    Hello {{item.id}}!
  </div>
</body>

La deuxième solution de BTW @Artem Andreev suggère qu'elle ne fonctionne pas dans Angular 1.1.4 et supérieur, tandis que la première ne résout pas le problème. Donc, j'ai peur pour l'instant que ce soit la solution la moins épineuse sans inconvénients de fonctionnalité

Agat
la source
Voulez-vous dire item.id? Si entity.id est ce que vous voulez dire, pouvez-vous expliquer? Merci beaucoup!
Gerfried
Oui tu as raison. Item.idest ce qu'il devrait b. Merci
Agat