![]()
ng-repeat is one of the most powerful directives in AngularJS, but when working with large datasets (1000+ items), it can cause serious performance issues. Each item in an ng-repeat list creates new watchers, increasing the digest cycle time and making the app slow.
This guide provides best practices and solutions to optimize ng-repeat for large datasets.
1. Why ng-repeat Causes Performance Issues?
When ng-repeat is used on large lists, AngularJS:
- Creates a new watcher for each element in the list.
- Tracks changes for every item during the digest cycle.
- Renders all elements even if they’re not visible on the screen.
- Re-renders elements unnecessarily when data changes.
Example of Performance Issue
<div ng-repeat="user in users">
{{ user.name }} - {{ user.email }}
</div>
If users contains 5000+ items, Angular must check all 5000 items in each digest cycle, slowing down the app.
2. How to Detect ng-repeat Performance Issues?
Run this in the Developer Console (F12 > Console) to check how many watchers exist in your app:
(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);
})();
If the number of watchers is over 2000, performance optimization is needed.
3. Optimizing ng-repeat for Large Datasets
Solution 1: Use track by to Prevent Duplicate Watchers
By default, Angular tracks objects by reference, causing unnecessary re-renders.
Use track by to optimize this.
Without track by (Slow Performance)
{{ user.name }}
</div>
With track by (Better Performance)
<div ng-repeat="user in users track by user.id">
{{ user.name }}
</div>
Why This Works?
track byprevents Angular from recreating the DOM when data updates.
Solution 2: Use Pagination Instead of Rendering Everything
If your dataset is large, display only a portion of it at a time.
Example: Paginate Data
<div ng-repeat="user in users | limitTo: itemsPerPage">
{{ user.name }}
</div>
<button ng-click="loadMore()">Load More</button>
$scope.itemsPerPage = 20;
$scope.loadMore = function () {
$scope.itemsPerPage += 20;
};
Why This Works?
- Limits the number of items rendered at a time, reducing watchers.
Solution 3: Use Virtual Scrolling for Large Lists
Instead of rendering all items at once, display only the visible portion using Angular’s ng-virtual-scroll.
Example Using Virtual Scroll
<virtual-scroll [items]="users">
<div *virtualItem="let user">
{{ user.name }}
</div>
</virtual-scroll>
Why This Works?
- It renders only visible items, improving performance.
- Ideal for datasets with 10,000+ items.
Solution 4: Use One-Time Binding (::) for Static Data
If the list does not change frequently, use one-time binding to remove unnecessary watchers.
Without One-Time Binding (Slow)
<div ng-repeat="user in users">
{{ user.name }}
</div>
With One-Time Binding (::) (Fast)
<div ng-repeat="user in ::users">
{{ user.name }}
</div>
Why This Works?
- Removes watchers after the initial binding.
Solution 5: Use ng-bind Instead of {{ }}
Using ng-bind is slightly faster than {{ }} because it updates only when needed.
Using {{ }} (Extra Watchers)
<div ng-repeat="user in users">
{{ user.name }}
</div>
Using ng-bind (Better Performance)
<div ng-repeat="user in users">
<span ng-bind="user.name"></span>
</div>
Why This Works?
ng-bindupdates efficiently compared to interpolation ({{ }}).
Solution 6: Avoid Filters in ng-repeat
Filters like orderBy and filter inside ng-repeat create new watchers. Instead, apply them in the controller.
Using Filters in ng-repeat (Slow)
<div ng-repeat="user in users | orderBy:'name'">
{{ user.name }}
</div>
Apply Filters in Controller (Fast)
$scope.sortedUsers = $filter('orderBy')($scope.users, 'name');
<div ng-repeat="user in sortedUsers">
{{ user.name }}
</div>
Why This Works?
- Filters run once instead of on every digest cycle.
Solution 7: Use bind once Directive
You can create a custom directive to remove watchers after initial binding.
Example: Custom bind-once Directive
app.directive('bindOnce', function () {
return {
scope: true,
link: function (scope, element, attrs) {
setTimeout(function () {
scope.$destroy();
}, 0);
}
};
});
<div ng-repeat="user in users" bind-once>
{{ user.name }}
</div>
Why This Works?
- Removes watchers after initial rendering.
