When working with custom directives in AngularJS, performance can suffer due to unnecessary re-renders caused by excessive $digest
cycle executions. Preventing these re-renders is crucial for optimizing application performance.
1. Why Do Directives Re-Render Unnecessarily?
Directives in AngularJS re-render when:
✔ Scope variables change → $watch
or $watchCollection
detects changes.
✔ Parent scope updates → Data-binding propagates updates.
✔ Frequent DOM updates → Overuse of ng-repeat
, ng-if
, or deep watches.
Example of Unnecessary Re-Render
app.directive("customDirective", function () {
return {
restrict: "E",
template: "<div>{{ data }}</div>",
scope: {
data: "="
}
};
});
Here, every time data
updates, the directive re-renders, even if the value hasn’t meaningfully changed.
2. Techniques to Prevent Unnecessary Re-Renders
1️⃣ Use One-Time Binding (::
)
If data doesn’t change after initial render, use one-time binding:
<custom-directive data="::staticData"></custom-directive>
This prevents AngularJS from watching for future updates.
2️⃣ Avoid Deep Watching ($watch
)
By default, $watch
tracks deep object changes, leading to unnecessary digest cycles. Instead, use shallow watching:
$scope.$watch("userData", function (newVal, oldVal) {
if (newVal !== oldVal) {
console.log("Updated!");
}
}, false); // false = Shallow watch (default)
Avoid deep watching (true
), which tracks every nested property change.
3️⃣ Use $watchCollection
Instead of $watch
For arrays/objects, $watchCollection
improves performance by only watching top-level changes:
$scope.$watchCollection("userList", function (newList, oldList) {
console.log("User list changed!");
});
4️⃣ Implement track by
in ng-repeat
ng-repeat
can cause re-renders if objects lack unique identifiers. Use track by
for efficiency:
<div ng-repeat="item in items track by item.id">
{{ item.name }}
</div>
This prevents unnecessary DOM manipulations when items remain unchanged.
5️⃣ Use bindOnce
for Performance (angular.bind
)
To bind scope variables only once inside directives:
link: function (scope) {
scope.boundValue = angular.copy(scope.data);
}
This prevents future re-evaluations.
6️⃣ Use scope: false
When No Isolated Scope Needed
If your directive doesn’t modify data, avoid creating an isolated scope:
app.directive("efficientDirective", function () {
return {
restrict: "E",
template: "<div>{{ parentData }}</div>",
scope: false // Uses parent scope, preventing unnecessary digest cycles
};
});
3. Optimized Directive Example
Here’s an optimized directive with minimal re-renders:
app.directive("optimizedDirective", function () {
return {
restrict: "E",
template: "<div>{{ ::data.name }}</div>", // One-time binding
scope: {
data: "="
},
link: function (scope) {
scope.localCopy = angular.copy(scope.data); // Prevents extra watchers
}
};
});
4. Best Practices for Avoiding Re-Renders
Use ::
one-time bindings for static data.
Minimize $watch
usage and prefer $watchCollection
.
Use track by
in ng-repeat
to prevent redundant re-renders.
Disable deep watching ($watch
with true
) unless necessary.
Use scope: false
if directive doesn’t need an isolated scope.
Leverage $cacheFactory
for caching repeated calculations.