I started learning and implementing Google’s Angular Javascript framework earlier this year for a side project. One of the application pages required sorting of an array of objects and displaying them on the page. However, that array of objects was already displayed on the page in a different arrangement, and I needed to show two separate sorted lists using the same data.
I made a separate view model parameter to hold a copy of the list that would be sorted in a the 2nd arrangement. Not knowing much about angular, I used a function as a property that would create a deep copy of the first array, as to not re-sort the 1st arrangement by reference.
An ugly javascript error then appeared in the console: Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
“Infdig” stands for “Infinite $digest Loop”, and means that some watched property of the view model is changing too much and can never resolve into a final value. In my case, the property function that was creating a deep copy of the first array and returning it as the value, was triggering an endless Angular digest loop because the end value was never resolved.
Example
I have an array of fruit objects that I want to display in two different ways on the page. First, I want to display all the fruits sorted by their name. Next, I want to display all the fruits sorted by their color’s name.
var fruits = [ { "name":"apple", "color":"red", }, { "name":"grape", "color":"purple", }, { "name":"banana", "color":"yellow", }, { "name":"avocado", "color":"green", } ];
Fruits sorted by name
apple | color: red
avocado | color: green
banana | color: yellow
grape | color: purpleFruits sorted by color name
avocado | color: green
grape | color: purple
apple | color: red
banana | color: yellow
Bad code – generates the infinite digest loop.
Good code – differently sorted arrays using the same data.
The wrong way to do this is to use a function as a parameter and always return a different value.
this.fruitsSortedByColor = function() { //copy the array to a new variable before sorting so we don't sort the other array by reference _fruits = []; angular.copy(fruits, _fruits); _fruits.sort(sortByColor); return _fruits; };
On the first digest loop, Angular assesses the property “this.fruitsSortedByColor” and runs through the lines of code defined in the function. A new array in memory is created on the line “angular.copy(fruits, _fruits);” and is then returned after being sorted. Because the array is new in memory, Angular interprets this as the model changing and fires off all the other watchers on the page, including another digest loop. The second digest loop comes around and the same thing happens until Angular throws a fit: “Okay, I’ve tried this 10 times now and no final unchanging value is ever returned, I’m giving up!”
The solution is to do the data copying once, and never in a property that is being evaluated (watched) on every digest loop.
//only do the array copying once, instead of every digest cycle this.fruitsSortedByColor = []; angular.copy(this.fruitsSortedByName, this.fruitsSortedByColor); this.fruitsSortedByColor.sort(sortByColor);
If any part of your model has changed, Angular will trigger a new digest loop to evaluate any modifications to the resulting view. Although this appears obvious to me now, it took me a few frustrating hours to understand this concept. Try to use parameter functions sparingly as they can easily fall into this endless infdig loop.
Javascript
var myApp = angular.module('myApp', []); myApp.controller('FruitCtrl', function() { this.fruitsSortedByName = fruits.sort(sortByName); //only do the array copying once, instead of every digest cycle this.fruitsSortedByColor = []; angular.copy(this.fruitsSortedByName, this.fruitsSortedByColor); this.fruitsSortedByColor.sort(sortByColor); function sortByName(f1, f2) { return (f1.name < f2.name) ? -1 : (f1.name > f2.name) ? 1 : 0; }; function sortByColor(f1, f2) { return (f1.color < f2.color) ? -1 : (f1.color > f2.color) ? 1 : 0; }; }); var fruits = [ { "name":"apple", "color":"red", }, { "name":"grape", "color":"purple", }, { "name":"banana", "color":"yellow", }, { "name":"avocado", "color":"green", } ];
HTML
<body ng-app="myApp"> <div ng-controller="FruitCtrl as fruitVm"> <h1>Fruits sorted by name</h1> <div ng-repeat="fruit in fruitVm.fruitsSortedByName"> {{fruit.name}} | color: {{fruit.color}} </div> <h1>Fruits sorted by color name</h1> <div ng-repeat="fruit in fruitVm.fruitsSortedByColor"> {{fruit.name}} | color: {{fruit.color}} </div> </div> </body>