![]()
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
- A user action (e.g., clicking a button) triggers
$scopechanges. - AngularJS enters the digest cycle and checks for updates.
- If it finds a change, it runs watchers (
$watch) to update the view. - If another change happens during this process, Angular runs another digest cycle.
- 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.
