ng-repeat performance issues with large datasets

Loading

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 by prevents 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-bind updates 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.

Leave a Reply

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