Filtre angulaire des œuvres, mais des causes “10 $digest itérations atteint”
Je recevoir des données à partir de mon serveur back-end structuré comme ceci:
{
name : "Mc Feast",
owner : "Mc Donalds"
},
{
name : "Royale with cheese",
owner : "Mc Donalds"
},
{
name : "Whopper",
owner : "Burger King"
}
De mon point de vue, je voudrais à "inverser" la liste. I. e. Je veux la liste de chaque propriétaire, et pour que le propriétaire de la liste de tous les hamburgers. Je peux réaliser cela en utilisant le underscorejs fonction groupBy
dans un filtre que j'utilise ensuite dans le ng-repeat
directive:
JS:
app.filter("ownerGrouping", function() {
return function(collection) {
return _.groupBy(collection, function(item) {
return item.owner;
});
}
});
HTML:
<li ng-repeat="(owner, hamburgerList) in hamburgers | ownerGrouping">
{{owner}}:
<ul>
<li ng-repeat="burger in hamburgerList | orderBy : 'name'">{{burger.name}}</li>
</ul>
</li>
Cela fonctionne comme prévu mais j'ai une énorme erreur de trace de la pile lorsque la liste est affichée avec le message d'erreur "10 $digest itérations atteint". J'ai du mal à voir comment mon code crée une boucle infinie qui est impliqué par ce message. Croit-on savoir pourquoi?
Voici un lien vers un débarquez avec le code: http://plnkr.co/edit/8kbVuWhOMlMojp0E5Qbs?p=preview
Vous devez vous connecter pour publier un commentaire.
Cela se produit parce que
_.groupBy
renvoie une collection de nouveau les objets chaque fois qu'il exécute. Angulaire dungRepeat
ne se rend pas compte que ces objets sont égaux parce quengRepeat
pistes par identité. Nouvel objet conduit à une nouvelle identité. Cela rend Angulaire pense que quelque chose a changé depuis la dernière vérification, ce qui signifie que Angulaires devez exécuter une autre case (aka digest). La prochaine digérer finit par obtenir encore un autre nouveau jeu d'objets, et donc une autre digest est déclenchée. Les répétitions jusqu'à ce Angulaire donne.Un moyen facile de se débarrasser de l'erreur est de s'assurer que votre filtre retourne la même collection d'objets à chaque fois (à moins que ça ait changé). Vous pouvez le faire très facilement avec un trait de soulignement en utilisant
_.memoize
. Juste envelopper la fonction de filtre dans memoize:Un résolveur fonction est nécessaire si vous prévoyez d'utiliser différentes valeurs du champ pour vos filtres. Dans l'exemple ci-dessus, la longueur du tableau est utilisé. Un mieux être de réduire la collecte d'une unique hash md5 de la chaîne.
Voir plunker fourche ici. Memoize souvenez-vous le fruit d'une entrée et de retour de l'objet même si l'entrée est la même qu'avant. Si la valeur change fréquemment si ensuite, vous devriez vérifier si
_.memoize
rejets anciens résultats pour éviter les fuites de mémoire au fil du temps.Enquête un peu plus loin je vois que
ngRepeat
prend en charge une syntaxe étendue... track by EXPRESSION
, ce qui peut être utile en quelque sorte, en vous permettant de dire Angulaire de regarder leowner
des restaurants à la place de l'identité des objets. Ce serait une alternative à la memoization astuce ci-dessus, bien que je ne parvenais pas à le tester dans le plunker (éventuellement de l'ancienne version de l'Angulaire de avanttrack by
a été mis en œuvre?).Bon, je pense que j'ai tout compris. Commencez par prendre un coup d'oeil à la le code source pour ngRepeat. Avis de la ligne 199: C'est là que nous avons mis en place des montres sur le tableau/objet de nous répéter, de sorte que, si elle ou de ses éléments de changer un digest cycle sera déclenchée:
Maintenant, nous avons besoin de trouver la définition de
$watchCollection
, qui commence à la ligne 360 de rootScope.js. Cette fonction est passée dans notre tableau ou d'un objet d'expression, qui dans notre cas esthamburgers | ownerGrouping
. Sur la ligne 365 que l'expression de chaîne est transformé en une fonction à l'aide de la$parse
service, une fonction qui sera appelé plus tard, et chaque fois que cet observateur s'exécute:Cette nouvelle fonction, qui permettra d'évaluer notre filtre et obtenir le tableau qui en résulte, est appelée à juste quelques lignes plus bas:
Donc
newValue
contient le résultat de nos données filtrées, après groupBy a été appliquée.Suivante, faites défiler jusqu'à la ligne de 408 et de prendre un coup d'oeil à ce code:
La première fois en cours d'exécution, oldValue est juste un tableau vide (défini ci-dessus comme "internalArray"), de sorte qu'un changement est détecté. Cependant, chacun de ses éléments seront mis à l'élément correspondant de newValue, de sorte que nous nous attendons à ce que la prochaine fois il fonctionne tout doit correspondre et aucun changement ne sera détecté. Alors, quand tout fonctionne normalement, ce code sera exécuté deux fois. Une fois le programme d'installation, qui détecte un changement à partir de l'initiale nulle état, puis, une fois encore, parce que le détectés forces de changement une nouvelle digérer cycle à exécuter. Dans le cas normal, aucun changement ne sera détecté lors de ce 2e terme, parce que à ce moment
(oldValue[i] !== newValue[i])
sera false pour tous je. C'est pourquoi vous avez été voir les 2 console.journal des sorties dans votre exemple.Mais dans votre cas d'échec, votre code de filtrage est la génération d'un nouveau tableau avec les nouveaux elments chaque fois qu'il est exécuté. Bien que ce nouveau tableau de elments ont la même valeur que les anciens éléments du tableau (c'est une copie parfaite), ils sont pas les mêmes éléments réels. Qui est, ils renvoient à des objets en mémoire que le fait de simplement arriver à avoir les mêmes propriétés et les valeurs. Donc dans votre cas
oldValue[i] !== newValue[i]
sera toujours vrai, pour la même raison que, par exemple,{x: 1} !== {x: 1}
est toujours vrai. Et un changement sera toujours détecté.De sorte que le problème essentiel est que votre filtre est la création d'une nouvelle copie du tableau chaque fois qu'il est exécuté, composé de de nouveaux éléments qui sont des copies de l'original du tableau elments. Donc, l'observateur de l'installation par ngRepeat obtient juste coincé dans ce qui est essentiellement une infinie boucle récursive, toujours de la détection d'un changement et le déclenchement d'un nouveau digérer cycle.
Voici une version simplifiée de votre code qui recrée le même problème: http://plnkr.co/edit/KiU4v4V0iXmdOKesgy7t?p=preview
Le problème disparaît si le filtre s'arrête la création d'un nouveau tableau à chaque fois qu'il est exécuté.
ngRepeat
ne se soucie pas de l'identité de la matrice, mais plutôt sur les éléments à l'intérieur le tableau. Se cette fourche qui renvoie toujours un nouveau tableau à chaque fois, mais les caches de l'élément interne: Pas d'erreurs 🙂 pendant ce temps, cette fourche permet de s'assurer de retourner la même matrice de tous les temps, mais crée un nouvel élément interne à chaque fois: Erreur.Nouveau à AngularJS 1.2 est un "suivi par" option pour les ng-repeat directive. Vous pouvez l'utiliser pour aider Angulaire de reconnaître que les différentes instances de l'objet devrait vraiment être considéré comme le même objet.
Cela aidera unconfuse Angulaire dans les cas comme le vôtre où vous êtes en utilisant un trait de Soulignement pour faire des poids lourds de trancher et couper en dés, en produisant de nouveaux objets, au lieu de se contenter de les filtrer.
Merci pour le memoize solution, il fonctionne très bien.
Cependant, _.memoize utilise la première passées en paramètre la clé par défaut pour son cache. Cela pourrait ne pas être très pratique, surtout si le premier paramètre sera toujours la même référence. J'espère que ce comportement est configurable via l'
resolver
paramètre.Dans l'exemple ci-dessous, le premier paramètre sera toujours la même matrice, et la seconde une chaîne de caractères représentant sur le champ dans lequel il doit être regroupées par:
Pardon la brièveté, mais essayez
ng-init="thing = (array | fn:arg)"
et l'utilisationthing
dans votreng-repeat
. Fonctionne pour moi mais c'est une vaste question.track by
dans d'autres circonstances. Merci!Je ne suis pas sûr pourquoi cette erreur est à venir, mais, logiquement, la fonction de filtre est appelée pour chaque élément de la matrice.
Dans votre cas, la fonction de filtre que vous avez créé retourne une fonction qui ne doit être appelée que lorsque le tableau est mis à jour, pas pour chaque élément du tableau. Le résultat renvoyé par la fonction peut alors être limité à html.
J'ai forké le plunker et ont créé ma propre mise en œuvre d'ici http://plnkr.co/edit/KTlTfFyVUhWVCtX6igsn
Il n'utilise pas de filtre. L'idée de base est d'appeler le
groupBy
au début et à chaque fois qu'un élément est ajoutéPour ce que ça vaut, pour ajouter encore un exemple et une solution, j'ai eu un simple filtre comme ceci:
avec:
qui a causé le décrit récursion infinie dans
$digest
. A été facilement résolu avec:C'est aussi nécessaire depuis
ngRepeat
, paradoxalement, n'aime pas les répéteurs, c'est à dire"foo\n\nfoo"
serait la cause d'une erreur à cause de deux paragraphes identiques. Cette solution peut ne pas être approprié si le contenu de ces paragraphes sont en train de changer et il est important qu'ils continuent à chercher à digérer, mais dans mon cas, ce n'est pas un problème.