angularjs infinite $digest Loop when no scope changes

foiseworth picture foiseworth · Nov 8, 2013 · Viewed 18.6k times · Source

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 effects? It just returns items from a list that have a property set to true.

The error only occurs when the first use of getDrawWithResults is on the page, if I remove, the error stops.

Uncaught Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [["getDrawsWithResults(selectedLottery.draws); newVal: []; oldVal: []"],["getDrawsWithResults(selectedLottery.draws); newVal: []; oldVal: []"],["getDrawsWithResults(selectedLottery.draws); newVal: []; oldVal: []"],["getDrawsWithResults(selectedLottery.draws); newVal: []; oldVal: []"],["getDrawsWithResults(selectedLottery.draws); newVal: []; oldVal: []"]]

This is my code:

HTML

<h4 ng-cloak ng-hide="getDrawsWithResults(selectedLottery.draws)">Results of Selected Lottery</h4>

<div class="con" ng-repeat="draw in getDrawsWithResults(selectedLottery.draws)" ng-cloak>
    <h5 class="con__header">[[ draw.date|date:'EEEE d MMMM yyyy - H:mm' ]]</h5>
    <div class="balls-list__outer con__row">
        <div class="balls-list">
            <div class="balls-list__ball__outer" ng-repeat="b in draw.results">
                <button class="balls-list__ball btn btn-con">[[ b ]]</button>
            </div>

        </div>
    </div>
</div>

JS

// return list of draws with results
$scope.getDrawsWithResults = function(draws) {
    return _.filter(draws, function(draw){
        return draw.results && draw.results.length > 0;
    });
}

Answer

Kos Prov picture Kos Prov · Nov 8, 2013

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.