Find an article

Start typing to search the archive.

← All articles

Fix Angular's digest iteration errors with memoization

Memoize your methods in JavaScript to avoid Angular performance problems and errors saying that 10 digest iterations have been reached.

Sometimes my Angular code failed with the exception Error: 10 $digest() iterations reached. Aborting!, and I had no idea what was going on until I got an explanation of how Angular’s bindings work. In this article, I will share an idea for avoiding the problem and improving Angular application performance with memoization.

The $digest problem

So when you get Error: 10 $digest() iterations reached. Aborting! in your Angular application, what does it actually mean? To answer this question, we have to understand how Angular detects changes and shows them in the UI immediately.

The algorithm is very simple: when you output a variable or function in HTML templates through bindings, a watcher is created. During the application’s lifecycle, watcher expressions are called many times and their results compared with previous values. If the values differ, an event is fired, and the new values are shown in the templates. For more information about change detection, refer to this article. But for now, it’s enough to understand that one possible issue is a function that returns an array of objects and is called in the digest cycle.

It’s very simple to demonstrate. Assume that we want to iterate through a list of users generated by a controller function:

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  var data = [
    {firstName: 'John', lastName: 'Smith'},
    {firstName: 'Andrei', lastName: 'Kaleshka'}
  ];

  $scope.getUsers = function() {
    var result = [];
    angular.forEach(data, function (user) {
      result.push({fullName: user.firstName + ' ' + user.lastName});
    });
    return result;
  }
});
<div ng-repeat="user in getUsers()">
  
</div>

Demo

The issue happens because getUsers() returns a different result on every call, even though the properties of the array items are the same:

var res1 = [{fullName: 'John Smith'}, {fullName: 'Andrei Kaleshka'}];
var res2 = [{fullName: 'John Smith'}, {fullName: 'Andrei Kaleshka'}];
res1 === res2; // false

That’s why Angular’s digest cycle will call getUsers indefinitely. The error Error: 10 $digest() iterations reached. Aborting! informs us about this.

Solution

To fix the problem we can cache the results of the function. For this purpose I prefer to use Lo-Dash’s memoize function:

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  var data = [
    {firstName: 'John', lastName: 'Smith'},
    {firstName: 'Andrei', lastName: 'Kaleshka'}
  ];

  $scope.getUsers = _.memoize(function() {
    var result = [];
    angular.forEach(data, function (user) {
      result.push({fullName: user.firstName + ' ' + user.lastName});
    });
    return result;
  });
});

Demo

Conclusion

Using this technique, we improve our Angular application’s performance, get rid of Error: 10 $digest() iterations reached. Aborting!, and implement functions on $scope that return arrays of objects and can be used in Angular templates.