How to Troubleshoot Angular "10 $digest() iterations reached" Error
Asked Answered
C

13

96

10 $digest() iterations reached. Aborting!

There is a lot of supporting text in the sense of "Watchers fired in the last 5 iterations: ", etc., but a lot of this text is Javascript code from various functions. Are there rules of thumb for diagnosing this problem? Is it a problem that can ALWAYS be mitigated, or are there applications complex enough that this issue should be treated as just a warning?

Cue answered 14/6, 2013 at 19:43 Comment(0)
G
85

as Ven said, you are either returning different (not identical) objects on each $digest cycle, or you are altering the data too many times.

The fastest solution to figure out which part of your app is causing this behavior is:

  1. remove all suspicious HTML - basically remove all your html from the template, and check if there are no warnings
  2. if there are no warnings - add small parts of the html you removed and check if the problem is back
  3. repeat step 2 until you get a warning - you will figure out which part of your html is responsible for the problem
  4. investigate further - the part from step 3 is responsible for either mutating the objects on the $scope or is returning non-identical objects on each $digest cycle.
  5. if you still have $digest iteration warnings after step 1, than you are probably doing something very suspicious. Repeat the same steps for parent template/scope/controller

You also want to make sure you are not altering the input of your custom filters

Keep in mind, that in JavaScript there are specific types of objects that don't behave like you would normally expect:

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
Gitt answered 16/6, 2013 at 0:55 Comment(5)
Thank you! This is a helpful heuristic. I'm also thinking that Angular's new "track by" feature for ngRepeat will help me, too. I'm doing some map() and groupBy() stuff using Underscore in a service, so it's definitely returning "different" objects each time (even though they logically represent the same things - "track by Id" would help Angular not see them as a "change" if they haven't really).Cue
If you are doing map() and groupBy() than make shure that your $watches perform dirty checking by objectEquality $watch('myFunctionDoingGropby',callback,true) see docs.angularjs.org/api/ng.$rootScope.Scope#$watchGitt
As per comment above, "track by" fixed my issue (see documents here: docs.angularjs.org/api/ng/directive/ngRepeat). I would be grateful for an explanatory comment explaining why this works...Reproductive
@g00fy: That was a good lead, and I was able to replace my list retrieval function with a variable on my $scope that gets assigned the _.map-ed list once - but in the general case, how would you influence said dirty checking by object equality if it's not a manually created $watch that is tripping up, but ngRepeat?Atavism
This worked for me. Helpful advice. Disabled all the HTML controls on the component and enabled them one by one to identify what was causing the issue. Before reading this answer, I wouldn't have thought to look at the HTML portion for the error. I was looking at my watchers and other variables (in vain) for hours!Cocoon
M
75

Usually that happens when you're returning a different object every time.

For example, if you use this in a ng-repeat:

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

You're going to get this error message because Angular tries to have the "stability" and will execute the function until it returns the same result 2 times (comparing with ===), which in our case will never return true because the function always returns a new object.

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

In this case, you can fix it by storing the object in scope directly, e.g.

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

That way you're always returning the same object!

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

(You should never encounter that, even on complex applications).

Update: Angular has added some more in-depth explanation on their website.

Muntin answered 14/6, 2013 at 19:56 Comment(8)
How do you stop this from occurring? I am having a similar situation right now.Vaccaro
Make sure you're not creating different objects on each call ;).Muntin
How can I ensure this? I am doing something like item in func(obj), but func seems to be called many times instead of once like I hope. I tried to use ng-init to call func and then attach it to a model on the scope but that did not work either.Vaccaro
@user1737909, how would you fix your $scope.getObj... example to resolve the error?Philbin
@KevinMeredith extract the object from the function (the function will only return it)Muntin
This is the example you see everywhere on this problem. It would be so awesome if Angular could point to the offending function, that has this behavior...Luellaluelle
The documentation for "Error: $rootScope:infdig Infinite $digest Loop" has a good, clear explanation of this; Angular puts the url in the error message, though it's hard to see! docs.angularjs.org/error/$rootScope/infdigAvailability
@BenWheeler It certainly improved since 2013, yes :P.Muntin
H
11

Just wanted to throw this solution in here, hopefully it'll help others. I was getting this iteration problem because I was iterating over a generated property which was making a new object every time it was called.

I fixed it by caching the generated object the first time it was requested, and then always returning the cache if it existed. A dirty() method was also added, which would destroy the cached results as needed.

I had something like this:

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

            return retObj;
        }
    });
}

And here's with the solution implemented:

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

            return _cached;
        }
    });

    myObj.dirty = function () {
        _cached = null;
    }
}
Honeysucker answered 26/7, 2014 at 5:22 Comment(1)
Oh my god I wish I could give you 10 upvotes. You just solved a problem which had me banging my head against the wall for nearly 3 hours. I love you!Resee
C
7

There also is the possibility of it not being an infinite loop at all. 10 iterations is not a sufficiently large number to conclude that with any amount of certainty. So before going on a wild-goose chase it may be advisable to rule out that possibility first.

The easiest method to do so is increasing the maximum digest loop count to a much larger number, which can be done in the module.config method, using the $rootScopeProvider.digestTtl(limit) method. If the infdig error does no longer appear you simply have some sufficiently complex update logic.

If you build data or views relying on recursive watches you may want to search for iterative solutions (i.e. not relying on new digest loops to be started) using while, for or Array.forEach. Sometimes the structure is just highly nested and not even recursive, there probably is not much to be done in those cases except raising the limit.

Another method of debugging the error is looking at the digest data. If you pretty print the JSON you get an array of arrays. Each top level entry represents an iteration, each iteration consists of a list of watch entries.

If you for example have a property which is modified in a $watch on itself it is easy to see that the value is changing infinitely:

$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
      }
   ]
]

Of course in larger project this may not be as simple, especially since the msg field often has the value "fn: regularInterceptedExpression" if the watch is a {{ }} interpolation.

Other than that the already mentioned methods, like cutting down the HTML to find the source of the problem, are of course helpful.

Celestinecelestite answered 16/8, 2016 at 17:53 Comment(0)
C
6

I had the same problem - I was creating a new date every time. So for anyone dealing with dates I converted all calls like this:

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

to:

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

Initializing a number instead of a date object solved it for me.

Cambridgeshire answered 30/1, 2015 at 22:23 Comment(0)
S
4

the easy way is : use angular.js,not the min file. open it and find the line:

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

add line below:

console.log("aaaa",watch)

and then refresh your page, in the develope tools console,you will find you error code .

Seventh answered 18/2, 2017 at 13:3 Comment(0)
F
1

It's a known bug in ui-router, this helped us: https://github.com/angular-ui/ui-router/issues/600

Flux answered 11/9, 2015 at 15:4 Comment(1)
That helped me tremendously! You should have definitely elaborate on this and add the code that solves the problem, which is on this reply: github.com/angular-ui/ui-router/issues/…Offering
D
0

I would also like to mention that I received this error message when I had a typo in the templateUrl of a custom directive that I had in my project. Due to the typo, the template could not be loaded.

/* @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'
    };

Look in the network tab of your web browser's dev tools, and look to see if any resource is having a 404 error.

Easy to overlook, because the error message is very cryptic and seemingly unrelated to the real issue.

Dowdy answered 31/7, 2016 at 4:47 Comment(0)
N
0

I was having this issue in my project because the .otherwise() was missing my route definition and I was hitting wrong route.

Nutmeg answered 10/8, 2016 at 17:50 Comment(0)
H
0

I had this issue because I was doing this

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

Instead of this: (notice = vs ===), my unit test started breaking and I found my stupidity

var variableExpense = this.lodash.find(product.variableExpenseList, (ve) => {
               return ve.rawMaterial.id === rawMaterial.id;
});
Hygroscope answered 12/2, 2017 at 1:32 Comment(0)
P
0

I ran into this issue where I needed a dynamic tooltip... it caused angular to recalculate it every time as a new value (even though it was the same). I created a function to cache the computed value like so:

$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;
    }
};

Then in the html, I accessed it like this:

<input type="text" ng-model="$ctrl.myObj.C" uib-tooltip-html="$ctrl.myObj.getTooltip().trusted">
Pulverize answered 23/3, 2017 at 21:55 Comment(0)
I
0

this is how I approached it and found a solution: I checked the text, it showed:

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

Watchers fired in the last 5 iterations: [[{"msg":"statement === statment && functionCall()","newVal":[{"id":7287,"referen...

so if you can see the

msg

that's the statment generating the error. I checked the function called in this message, I returned (false) from all of them just to determine which one have the problem. one of them was calling a function that keeps changing the return, which is the problem.

Intertwist answered 18/1, 2019 at 14:10 Comment(0)
E
-4

As crazy as it sounds, I fixed this error just by restarting my browser when it just cropped up all of a sudden.

So one solution is to just clear your browser's cache or try restarting the browser.

Ern answered 12/10, 2017 at 11:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.