Avoiding excessive watchers in AngularJS

Loading

In AngularJS, excessive watchers can slow down an application due to frequent digest cycles. Each watcher adds a performance cost because AngularJS continuously checks for changes in watched variables. This guide explains how to reduce unnecessary watchers and optimize performance.


1. Understanding Watchers and the Digest Cycle

AngularJS uses the digest cycle to detect changes in the application. Every $scope.$watch(), ng-model, ng-repeat, ng-if, and ng-show directive adds a watcher.

Problem:

  • Too many watchers = Longer digest cycles
  • UI lag, slow updates, and poor user experience

Example of Watcher Growth:

$scope.count = 0;
$scope.$watch("count", function(newValue, oldValue) {
console.log("Count changed:", newValue);
});
  • If thousands of watchers are added, the digest cycle takes longer.

2. How to Identify Excessive Watchers?

Using Chrome DevTools

1️⃣ Open Chrome DevTools (F12 or Ctrl + Shift + I).
2️⃣ Go to the Console tab.
3️⃣ Run the following command:

angular.element(document).injector().get('$rootScope').$$watchersCount

4️⃣ It returns the total number of watchers in your app.


3. Techniques to Reduce Watchers in AngularJS

1. Use ng-if Instead of ng-show

  • ng-show: Keeps the element in the DOM but toggles visibility (watchers remain active).
  • ng-if: Removes the element from the DOM when false (removes watchers).
<!-- Inefficient -->
<div ng-show="isVisible">This div always exists in the DOM.</div>

<!-- Efficient -->
<div ng-if="isVisible">This div is removed when not needed.</div>

Better Performance: ng-if destroys scope watchers when the element is removed.


2. Use one-time binding (::) for Static Data

If a value does not change, prevent AngularJS from watching it.

<!-- Inefficient -->
<h1>{{ user.name }}</h1> <!-- Always watched -->

<!-- Efficient -->
<h1>{{ ::user.name }}</h1> <!-- One-time binding (not watched) -->

Reduces watchers because AngularJS does not track changes after the first load.


3. Optimize ng-repeat with track by

ng-repeat creates a watcher for each item in an array.

Problem:

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

Solution: Use track by to improve performance.

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

✔ Prevents Angular from re-creating the DOM unnecessarily.


4. Limit the Use of $watch

Avoid unnecessary $watch() calls in controllers.

Inefficient:

$scope.$watch("data", function(newVal) {
console.log("Data changed!");
}, true); // Deep watch (bad for performance)

Efficient:

$scope.$watchCollection("data", function(newVal) {
console.log("Data changed!");
});

$watchCollection() only watches first-level changes, reducing processing load.


5. Remove Watchers When No Longer Needed

  • $watch() returns a deregistration function. Use it to manually remove watchers.
var unwatch = $scope.$watch("someVar", function(newVal) {
console.log("Value changed:", newVal);
});

// Remove the watcher when done
unwatch();

✔ Useful for cleaning up unused watchers.


6. Use $scope.$evalAsync() Instead of $apply()

  • $apply() triggers a full digest cycle (expensive).
  • $evalAsync() schedules a digest only if needed.
$scope.$evalAsync(function() {
console.log("Executed with less overhead.");
});

✔ Reduces unnecessary digest cycles.


7. Use Web Workers for Heavy Computation

  • Move expensive calculations outside AngularJS scope using Web Workers.

Without Web Worker (Blocks UI)

$scope.processData = function() {
for (let i = 0; i < 1000000000; i++) {
// Expensive computation
}
};

With Web Worker (Better Performance)

var worker = new Worker("worker.js");
worker.postMessage(data);
worker.onmessage = function(event) {
$scope.result = event.data;
};

✔ Keeps the UI responsive.


4. Summary – Best Practices to Reduce Watchers

TechniqueBenefit
Use ng-if instead of ng-showRemoves watchers when not needed
Use :: one-time bindingPrevents Angular from tracking static values
Use track by in ng-repeatAvoids unnecessary DOM updates
Avoid deep $watch()Use $watchCollection() for better performance
Manually remove watchersDeregister watchers when they are no longer needed
Use $scope.$evalAsync()Minimizes digest cycle triggers
Move heavy processing to Web WorkersKeeps UI responsive

Leave a Reply

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