Comment dépanner l'erreur angulaire "10 $ digest () itérations atteintes"

91

10 itérations $ digest () atteintes. Abandon!

Il y a beaucoup de texte de soutien dans le sens de "Watchers déclenchés dans les 5 dernières itérations:", etc., mais une grande partie de ce texte est du code Javascript provenant de diverses fonctions. Existe-t-il des règles empiriques pour diagnostiquer ce problème? Est-ce un problème qui peut TOUJOURS être atténué, ou existe-t-il des applications suffisamment complexes pour que ce problème soit traité comme un simple avertissement?

blaster
la source

Réponses:

80

comme Ven l'a dit, soit vous retournez des objets différents (non identiques) à chaque $digestcycle, soit vous modifiez les données trop souvent.

La solution la plus rapide pour déterminer quelle partie de votre application est à l'origine de ce comportement est:

  1. supprimez tout HTML suspect - supprimez essentiellement tout votre code HTML du modèle et vérifiez s'il n'y a pas d'avertissement
  2. s'il n'y a pas d'avertissement - ajoutez de petites parties du html que vous avez supprimé et vérifiez si le problème est de retour
  3. répétez l'étape 2 jusqu'à ce que vous obteniez un avertissement - vous saurez quelle partie de votre html est responsable du problème
  4. approfondissez votre recherche - la partie de l'étape 3 est responsable de la mutation des objets sur le $scopeou renvoie des objets non identiques à chaque $digestcycle.
  5. si vous avez toujours des $digestavertissements d'itération après l'étape 1, vous faites probablement quelque chose de très suspect. Répétez les mêmes étapes pour le modèle / la portée / le contrôleur parent

Vous voulez également vous assurer de ne pas modifier l'entrée de vos filtres personnalisés

Gardez à l'esprit que dans JavaScript, il existe des types spécifiques d'objets qui ne se comportent pas comme vous le souhaiteriez normalement:

new Boolean(true) === new Boolean(true) // false
new Date(0) == new Date(0) // false
new String('a') == new String('a') // false
new Number(1) == new Number(1) // false
[] == [] // false
new Array == new Array // false
({})==({}) // false
g00fy
la source
5
Je vous remercie! C'est une heuristique utile. Je pense aussi que la nouvelle fonctionnalité "track by" d'Angular pour ngRepeat m'aidera aussi. Je fais des trucs de map () et groupBy () en utilisant Underscore dans un service, donc il renvoie définitivement des objets "différents" à chaque fois (même s'ils représentent logiquement les mêmes choses - "track by Id" aiderait Angular à ne pas les voir comme un "changement" s'ils ne l'ont pas vraiment fait).
blaster
2
Si vous faites map()et groupBy()que assurez-vous que vos $watches effectuent une vérification sale par objectEquality $watch('myFunctionDoingGropby',callback,true) voir docs.angularjs.org/api/ng.$rootScope.Scope#$watch
g00fy
Comme pour le commentaire ci-dessus, "track by" a résolu mon problème (voir les documents ici: docs.angularjs.org/api/ng/directive/ngRepeat ). Je serais reconnaissant pour un commentaire explicatif expliquant pourquoi cela fonctionne ...
Soferio
@ g00fy: C'était une bonne piste, et j'ai pu remplacer ma fonction de récupération de liste par une variable sur my $scopequi se voit attribuer la _.mapliste -ed une fois - mais dans le cas général, comment influenceriez-vous ladite vérification sale par l'égalité d'objet si ce n'est pas un créé manuellement $watchqui trébuche, mais ngRepeat?
OR Mapper du
73

Cela se produit généralement lorsque vous renvoyez un objet différent à chaque fois.

Par exemple, si vous utilisez ceci dans un ng-repeat:

$scope.getObj = function () {
  return [{a: 1}, {b: 2}];
};

Vous allez recevoir ce message d'erreur car Angular essaie d'avoir la "stabilité" et exécutera la fonction jusqu'à ce qu'elle renvoie le même résultat 2 fois (en comparant avec ===), qui dans notre cas ne retournera jamais vrai car la fonction renvoie toujours un nouvel objet.

console.log({} === {}); // false. Those are two different objects!

Dans ce cas, vous pouvez le corriger en stockant l'objet directement dans la portée, par exemple

$scope.objData = [{a: 1}, {b: 2}];
$scope.getObj = function () {
  return $scope.objData;
};

De cette façon, vous retournez toujours le même objet!

console.log($scope.objData === $scope.objData); // true (a bit obvious...)

(Vous ne devriez jamais rencontrer cela, même sur des applications complexes).

Mise à jour: Angular a ajouté des explications plus détaillées sur leur site Web .

Ven
la source
Comment empêchez-vous cela de se produire? J'ai une situation similaire en ce moment.
Conquérant
2
Assurez-vous de ne pas créer d'objets différents à chaque appel;).
Ven
Comment puis-je m'en assurer? Je fais quelque chose comme item dans func (obj), mais func semble être appelé plusieurs fois au lieu d'une fois comme je l'espère. J'ai essayé d'utiliser ng-init pour appeler func puis l'attacher à un modèle sur l'oscilloscope, mais cela n'a pas fonctionné non plus.
Conquérant
1
C'est l'exemple que vous voyez partout sur ce problème. Ce serait tellement génial si Angular pouvait pointer vers la fonction incriminée, qui a ce comportement ...
Jorn
1
@BenWheeler Cela s'est certainement amélioré depuis 2013, oui: P.
Ven
11

Je voulais juste jeter cette solution ici, j'espère que cela aidera les autres. J'avais ce problème d'itération parce que j'effectuais une itération sur une propriété générée qui créait un nouvel objet à chaque fois qu'il était appelé.

Je l'ai corrigé en mettant en cache l'objet généré la première fois qu'il était demandé, puis en renvoyant toujours le cache s'il existait. Une méthode dirty () a également été ajoutée, ce qui détruirait les résultats mis en cache si nécessaire.

J'avais quelque chose comme ça:

function MyObj() {
    var myObj = this;
    Object.defineProperty(myObj, "computedProperty" {
        get: function () {
            var retObj = {};

            return retObj;
        }
    });
}

Et voici la solution mise en œuvre:

function MyObj() {
    var myObj = this,
        _cached;
    Object.defineProperty(myObj, "computedProperty" {
        get: function () {
            if ( !_cached ) {
                _cached = {};
            }

            return _cached;
        }
    });

    myObj.dirty = function () {
        _cached = null;
    }
}
Asmor
la source
Oh mon dieu, j'aimerais pouvoir vous donner 10 votes positifs. Vous venez de résoudre un problème qui m'a fait cogner la tête contre le mur pendant près de 3 heures. Je t'aime!
GMA
7

Il y a aussi la possibilité que ce ne soit pas du tout une boucle infinie. 10 itérations n'est pas un nombre suffisamment grand pour en conclure avec une certaine certitude. Donc, avant de se lancer dans une chasse à l'oie sauvage, il peut être conseillé d'exclure d'abord cette possibilité.

La méthode la plus simple pour ce faire consiste à augmenter le nombre maximal de boucles de condensé à un nombre beaucoup plus grand, ce qui peut être fait dans la module.configméthode, à l'aide de la $rootScopeProvider.digestTtl(limit)méthode. Si l' infdigerreur n'apparaît plus, vous disposez simplement d'une logique de mise à jour suffisamment complexe.

Si vous créez des données ou des vues reposant sur des montres récursives, vous pouvez rechercher des solutions itératives (c'est-à-dire ne pas compter sur de nouvelles boucles de condensé pour démarrer) en utilisant while, forouArray.forEach . Parfois, la structure est simplement très imbriquée et même pas récursive, il n'y a probablement pas grand-chose à faire dans ces cas, à part augmenter la limite.

Une autre méthode de débogage de l'erreur consiste à examiner les données de résumé. Si vous imprimez assez le JSON, vous obtenez un tableau de tableaux. Chaque entrée de niveau supérieur représente une itération, chaque itération se compose d'une liste d'entrées de surveillance.

Si vous avez par exemple une propriété qui est modifiée en a $watchsur elle-même, il est facile de voir que la valeur change à l'infini:

$scope.vm.value1 = true;
$scope.$watch("vm.value1", function(newValue)
{
    $scope.vm.value1 = !newValue;
});
[
   [
      {
         "msg":"vm.value1",
         "newVal":true,
         "oldVal":false
      }
   ],
   [
      {
         "msg":"vm.value1",
         "newVal":false,
         "oldVal":true
      }
   ],
   [
      {
         "msg":"vm.value1",
         "newVal":true,
         "oldVal":false
      }
   ],
   [
      {
         "msg":"vm.value1",
         "newVal":false,
         "oldVal":true
      }
   ],
   [
      {
         "msg":"vm.value1",
         "newVal":true,
         "oldVal":false
      }
   ]
]

Bien sûr, dans un projet plus important, cela peut ne pas être aussi simple, d'autant plus que le msgchamp a souvent la valeur "fn: regularInterceptedExpression"si la montre est une {{ }}interpolation.

En dehors de cela, les méthodes déjà mentionnées, comme la réduction du code HTML pour trouver la source du problème, sont bien sûr utiles.

HB
la source
6

J'avais le même problème - je créais une nouvelle date à chaque fois. Donc, pour tous ceux qui ont affaire à des dates, j'ai converti tous les appels comme ceci:

var date = new Date(); // typeof returns object

à:

var date = new Date().getTime(); // typeof returns number

L'initialisation d'un nombre au lieu d'un objet de date l'a résolu pour moi.

Arman Bimatov
la source
2

le moyen le plus simple est: utilisez angular.js, pas le fichier min. ouvrez-le et trouvez la ligne:

if ((dirty || asyncQueue.length) && !(ttl--)) {

ajouter une ligne ci-dessous:

console.log("aaaa",watch)

puis actualisez votre page, dans la console des outils de développement, vous trouverez votre code d'erreur.

Askie
la source
0

Je voudrais également mentionner que j'ai reçu ce message d'erreur lorsque j'ai eu une faute de frappe dans le templateUrl d'une directive personnalisée que j'avais dans mon projet. En raison de la faute de frappe, le modèle n'a pas pu être chargé.

/* @ngInject */
function topNav() {
    var directive = {
        bindToController: true,
        controller: TopNavController,
        controllerAs: 'vm',
        restrict: 'EA',
        scope: {
            'navline': '=',
            'sign': '='
        },
        templateUrl: 'app/shared/layout/top-navTHIS-IS-A-TYPO.html'
    };

Regardez dans l'onglet réseau des outils de développement de votre navigateur Web et vérifiez si une ressource présente une erreur 404.

Facile à ignorer, car le message d'erreur est très cryptique et apparemment sans rapport avec le vrai problème.

Casey Plummer
la source
0

J'avais ce problème dans mon projet car le .otherwise () manquait ma définition de route et je frappais une mauvaise route.

Sandeep M
la source
0

J'ai eu ce problème parce que je faisais ça

var variableExpense = this.lodash.find(product.variableExpenseList, (ve) => {
               return ve.rawMaterial.id = rawMaterial.id;
});

Au lieu de ceci: (notice = vs ===), mon test unitaire a commencé à casser et j'ai trouvé ma stupidité

var variableExpense = this.lodash.find(product.variableExpenseList, (ve) => {
               return ve.rawMaterial.id === rawMaterial.id;
});
Maccurt
la source
0

J'ai rencontré ce problème où j'avais besoin d'une info-bulle dynamique ... cela a amené angular à la recalculer à chaque fois en tant que nouvelle valeur (même si c'était la même chose). J'ai créé une fonction pour mettre en cache la valeur calculée comme ceci:

$ctrl.myObj = {
    Title: 'my title',
    A: 'first part of dynamic toolip',
    B: 'second part of dynamic tooltip',
    C: 'some other value',
    getTooltip: function () {
        // cache the tooltip
        var obj = this;
        var tooltip = '<strong>A: </strong>' + obj.A + '<br><strong>B: </strong>' + obj.B;
        var $tooltip = {
            raw: tooltip,
            trusted: $sce.trustAsHtml(tooltip)
        };
        if (!obj.$tooltip) obj.$tooltip = $tooltip;
        else if (obj.$tooltip.raw !== tooltip) obj.$tooltip = $tooltip;
        return obj.$tooltip;
    }
};

Puis dans le html, je l'ai accédé comme ceci:

<input type="text" ng-model="$ctrl.myObj.C" uib-tooltip-html="$ctrl.myObj.getTooltip().trusted">
Scrappy Doo
la source
0

voici comment je l'ai abordé et j'ai trouvé une solution: j'ai vérifié le texte, il a montré:

Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!

Observateurs lancés au cours des 5 dernières itérations: [[{"msg": "statement === statment && functionCall ()", "newVal": [{"id": 7287, "referen ...

donc si vous pouvez voir le

msg

c'est la déclaration qui génère l'erreur. J'ai vérifié la fonction appelée dans ce message, j'ai renvoyé (faux) de tous juste pour déterminer lequel a le problème. l'un d'eux appelait une fonction qui ne cesse de changer le retour, ce qui est le problème.

Ahmed M. Matar
la source
-3

Aussi fou que cela puisse paraître, j'ai corrigé cette erreur simplement en redémarrant mon navigateur alors qu'il venait de surgir tout d'un coup.

Une solution consiste donc simplement à vider le cache de votre navigateur ou à essayer de redémarrer le navigateur.

cst1992
la source