In AngularJS, the digest cycle is responsible for updating the UI when model data changes. However, if too many digest cycles are triggered, it can cause severe UI lag, slow performance, and unresponsive pages.
This guide will break down why excessive digest cycles happen and how to optimize AngularJS performance effectively.
1. Understanding the AngularJS Digest Cycle
How the Digest Cycle Works?
- When the model (
$scope
data) changes, AngularJS starts a digest cycle. - It checks all watchers (
$watch
expressions) to detect changes. - If a change is found, it runs another cycle to check for further updates.
- This process repeats until no further changes are detected (max: 10 iterations).
Example of How AngularJS Triggers a Digest Cycle
$scope.name = "John"; // A digest cycle is triggered automatically
Any Angular directive, like ng-model
, ng-click
, or ng-repeat
, can also trigger digest cycles.
2. Symptoms of Excessive Digest Cycles
If your AngularJS app is slow, check for these common symptoms:
Laggy UI: UI updates feel slow, especially on user input events.
High CPU Usage: The browser’s CPU usage spikes, even when idle.
Slow Animations: CSS transitions and JavaScript animations stutter.
Console Errors: You see 10 digest iterations reached
errors.
Too Many Watchers: Running the following script in the browser console shows thousands of watchers:
(function () {
var totalWatchers = 0;
var countWatchers = function (element) {
if (angular.element(element).scope()) {
totalWatchers += angular.element(element).scope().$$watchers ? angular.element(element).scope().$$watchers.length : 0;
}
angular.forEach(element.children, function (child) {
countWatchers(child);
});
};
countWatchers(document.body);
console.log("Total Watchers: ", totalWatchers);
})();
3. Common Causes of Excessive Digest Cycles & Solutions
Cause 1: Too Many Watchers ($watch
, $watchCollection
)
Every ng-model
, ng-repeat
, and $watch
adds watchers. More watchers mean longer digest cycles.
Bad Example (Too Many Watchers)
$scope.$watch('user.name', function (newVal, oldVal) {
console.log("User changed:", newVal);
});
Each $watch
adds overhead to the digest cycle.
Solution: Use One-Time Binding (::
)
<p>{{ ::user.name }}</p>
Why? Removes the watcher after initial binding, reducing digest load.
Cause 2: Unnecessary $apply()
Calls
Calling $scope.$apply()
inside event listeners triggers digest cycles unnecessarily.
Bad Example ($apply()
inside setTimeout)
setTimeout(function () {
$scope.$apply(function () {
$scope.name = "John";
});
}, 1000);
Fix: Use $timeout
Instead (Optimized for AngularJS)
$timeout(function () {
$scope.name = "John";
});
Why? $timeout
automatically triggers digest cycles efficiently.
Cause 3: Using $digest()
Inside a Loop
Calling $scope.$digest()
inside a loop triggers multiple digest cycles, slowing down performance.
Bad Example (Multiple $digest()
Calls)
for (var i = 0; i < 100; i++) {
$scope.$digest(); // Triggers 100 digest cycles!
}
Fix: Batch Updates and Use $evalAsync()
$scope.$evalAsync(function () {
for (var i = 0; i < 100; i++) {
$scope.list.push(i);
}
});
Why? $evalAsync()
schedules a digest cycle only once after the loop.
Cause 4: Heavy ng-repeat
with Large Lists
Using ng-repeat
with thousands of items creates excessive watchers, leading to slow digest cycles.
Bad Example (Rendering Large List)
<div ng-repeat="item in items">
{{ item.name }}
</div>
Fix: Use track by
, Pagination, or Virtual Scroll
<div ng-repeat="item in items track by item.id">
{{ item.name }}
</div>
Why? Prevents Angular from recreating DOM elements unnecessarily.
Cause 5: Filters Inside ng-repeat
Applying filters directly inside ng-repeat
triggers digest cycles for every item.
Bad Example (Filter in ng-repeat
)
<div ng-repeat="user in users | orderBy:'name'">
{{ user.name }}
</div>
Fix: Apply Filters in the Controller
$scope.sortedUsers = $filter('orderBy')($scope.users, 'name');
<div ng-repeat="user in sortedUsers">
{{ user.name }}
</div>
Why? Filters run once, reducing digest cycles.
Cause 6: Too Many ng-if
Directives
Using ng-if
instead of ng-show
removes and recreates DOM elements, triggering extra digest cycles.
Bad Example (ng-if
Overuse)
<div ng-if="isVisible">Visible</div>
Fix: Use ng-show
Instead
<div ng-show="isVisible">Visible</div>
Why? ng-show
just toggles display: none
, avoiding DOM recreation.
4. Best Practices to Optimize Digest Cycles
Issue | Solution |
---|---|
Too many watchers slowing down digest cycles | Use one-time binding (:: ), minimize $watch usage |
Unnecessary $apply() calls triggering multiple cycles | Use $timeout or $evalAsync() instead |
Loops triggering $digest() multiple times | Batch updates and use $evalAsync() |
Large lists causing slow digest cycles | Use pagination, virtual scroll, track by |
Filters inside ng-repeat triggering excessive checks | Move filters to the controller |
ng-if creating unnecessary DOM changes | Use ng-show instead |