Optimizing AngularJS digest cycle for better performance

Loading

What You’ll Learn

What the AngularJS Digest Cycle is
Common performance issues
Best practices to optimize the digest cycle
Using $applyAsync, $evalAsync, and $timeout
Avoiding unnecessary watchers


1️⃣ Understanding the AngularJS Digest Cycle

What is the Digest Cycle?

The digest cycle is a loop in AngularJS that checks for changes in the scope and updates the DOM accordingly.

How It Works

  1. A user action (e.g., clicking a button) triggers $scope changes.
  2. AngularJS enters the digest cycle and checks for updates.
  3. If it finds a change, it runs watchers ($watch) to update the view.
  4. If another change happens during this process, Angular runs another digest cycle.
  5. The loop continues until no changes are detected.

Performance Bottlenecks

  • Too many watchers slow down the digest cycle.
  • Heavy DOM manipulations cause re-renders.
  • Nested digest cycles lead to performance drops.

2️⃣ Finding Performance Issues

Checking the Number of Watchers

You can check the total number of watchers in your app using the following code in the browser console:

(function () {
var root = angular.element(document.getElementsByTagName('body'));
var watchers = [];

var countWatchers = function (element) {
angular.forEach(['$scope', '$isolateScope'], function (scopeProperty) {
if (element.data() && element.data().hasOwnProperty(scopeProperty)) {
angular.forEach(element.data()[scopeProperty].$$watchers, function (watcher) {
watchers.push(watcher);
});
}
});

angular.forEach(element.children(), function (childElement) {
countWatchers(angular.element(childElement));
});
};

countWatchers(root);
console.log("Total watchers in the app: ", watchers.length);
})();

If your app has over 2000 watchers, you should consider optimizing the digest cycle.


3️⃣ Best Practices to Optimize the Digest Cycle

1. Reduce the Number of Watchers

Each ng-repeat, ng-model, and ng-show directive adds watchers to the scope.

Use track by in ng-repeat to improve performance.
Bad:

<div ng-repeat="item in items">
{{item.name}}
</div>

Optimized:

<div ng-repeat="item in items track by item.id">
{{item.name}}
</div>

track by item.id prevents AngularJS from rechecking all elements.


2. Use One-Time Binding for Static Data

If a value doesn’t change, use one-time binding (::) to reduce the number of watchers.

Bad:

<h1>{{title}}</h1>

Optimized:

<h1>{{::title}}</h1>

The :: tells AngularJS to remove the watcher after the first evaluation.


3. Use $applyAsync for Batch Processing

By default, $apply() triggers the digest cycle immediately, which can be expensive.

Instead, use $applyAsync() to delay execution and batch multiple changes together.

Bad:

$scope.updateData = function () {
$scope.data = newData;
$scope.$apply(); // Immediately triggers the digest cycle
};

Optimized:

$scope.updateData = function () {
$scope.data = newData;
$scope.$applyAsync(); // Schedules the digest cycle asynchronously
};

Fewer digest cycles = Better performance.


4. Use $evalAsync to Prevent Nested Digest Cycles

If you trigger a digest cycle inside another digest cycle, it can slow down performance.
Use $evalAsync() instead to defer execution until the current digest cycle is finished.

Bad (Nested Digest Cycle):

$scope.$watch("value", function () {
$scope.anotherValue = "Updated";
$scope.$apply(); // Triggers another digest cycle
});

Optimized:

$scope.$watch("value", function () {
$scope.$evalAsync(function () {
$scope.anotherValue = "Updated";
});
});

$evalAsync() ensures the change is processed after the current cycle, avoiding unnecessary loops.


5. Use $timeout Instead of $apply for Async Code

$apply() forces a full digest cycle. If you need to run asynchronous operations like API calls, use $timeout() instead.

Bad:

setTimeout(function () {
$scope.data = "Updated";
$scope.$apply(); // Forces a digest cycle
}, 1000);

Optimized:

$timeout(function () {
$scope.data = "Updated"; // Angular automatically triggers digest
}, 1000);

$timeout() automatically runs a digest without forcing a full cycle.


6. Limit the Use of ng-show and ng-hide

ng-show and ng-hide add watchers that constantly check conditions.

Use ng-if instead, which removes elements from the DOM.

Bad (Keeps the element in the DOM, adding watchers):

<div ng-show="isVisible">Hello</div>

Optimized (Removes the element from the DOM when isVisible is false):

<div ng-if="isVisible">Hello</div>

ng-if improves performance by preventing unnecessary checks.


7. Avoid Deep-Watching Objects

By default, $watch performs deep checks on objects, which can slow down performance.

Bad:

$scope.$watch("user", function (newValue, oldValue) {
console.log("User updated!");
}, true); // Deep watch

Optimized:

$scope.$watch("user.name", function (newValue, oldValue) {
console.log("User name updated!");
}); // Shallow watch

Avoid deep-watching unless absolutely necessary.


8. Use bindonce for Static Content

The angular-bindonce library helps remove watchers from elements that don’t need updates.

Install:

bower install angular-bindonce

Usage:

<h1 bindonce>{{title}}</h1>

Reduces watcher count significantly.

Leave a Reply

Your email address will not be published. Required fields are marked *