Skip to content Skip to sidebar Skip to footer

Angularjs Infinite $digest Loop When No Scope Changes

I'm getting the below error in my angular code. I'm struggling to understand why the function getDrawWithResults would cause a digest cycle as there don't seem to be any side effec

Solution 1:

I assume _.filter returns a new array instance everytime it is run. This causes angular's implicit $watches like:

ng-hide="getDrawsWithResults(selectedLottery.draws)"

and

ng-repeat="draw in getDrawsWithResults(selectedLottery.draws)"

to think that the model has changed so it needs to digest again.

I would implement a filter

app.filter('withResults', function() {
    return function(draws) {
        return _.filter(draws, function(draw){
            return draw.results && draw.results.length > 0;
        });
    }
})

and apply it like that (see EDIT below):

ng-hide="selectedLottery.draws | withResults"

and

ng-repeat="draw in selectedLottery.draws | withresults"

EDITED after discussion in comments

The actual problem is this binding:

ng-hide="getDrawsWithResults(selectedLottery.draws)"

ng-hide registers a watch which will fire forever since the reference of the filtered array always changes. It can be changed to:

ng-hide="getDrawsWithResults(selectedLottery.draws).length > 0"

and the corresponding filter:

ng-hide="(selectedLottery.draws | withResults).length > 0"

ng-repeat does not have the same problem because it registers a $watchCollection.


Solution 2:

This implies $scope.getDrawsWithResults() is not idempotent. Given the same input and state it doesn't consistently return the same result. And ng-hide requires an idempotent function (as do all function that Angular has a watch on).

In short, you may be better off using a function that returns a single boolean result instead of _.filter which returns an array. Perhaps _.all?

The reason idempotence matters here is because of the way Angular's $digest cycle works. Because of your ng-hide Angular places a watch on the results of your $scope.getDrawsWithResults(). This way it can be alerted whenever it should re-evaluate that ng-hide. Your ng-repeat is not affected because it's results don't need to be watched by Angular.

So every time a $digest happens (which is many times a second) $scope.getDrawsWithResults() is called to see if it's results changed from the previous $digest cycle and thus whether it should change ng-hide. If the result has changed Angular knows that could also mean some other function it's watching (which possibly uses a result from your function) could have changed. So it needs to re-run $digest (letting the change propagate through the system if need be).

And so $digest keeps running until the results of all functions it's watching stop changing. Or until there's been 10 $digest cycles. Angular assumes that if the system isn't stable after 10 cycles it probably will never stabilise. And so it gives up and throws the error message you got.

You can dive into this all in more depth here if you'd like: http://teropa.info/blog/2013/11/03/make-your-own-angular-part-1-scopes-and-digest.html


Post a Comment for "Angularjs Infinite $digest Loop When No Scope Changes"