Why is ng-repeat in AngularJS slow with large lists?
Useful prerequisites for analyzing performance
Time logging directive:
// Post repeat directive for logging the rendering time angular.module('siApp.services').directive('postRepeatDirective', ['$timeout', '$log', 'TimeTracker', function($timeout, $log, TimeTracker) { return function(scope, element, attrs) { if (scope.$last){ $timeout(function(){ var timeFinishedLoadingList = TimeTracker.reviewListLoaded(); var ref = new Date(timeFinishedLoadingList); var end = new Date(); $log.debug("## DOM rendering list took: " + (end - ref) + " ms"); }); } }; } ]); // Use in HTML: <tr ng-repeat="item in items" post-repeat-directive>…</tr>
Timeline feature of Chrome developer tools
Basic tuning by limiting size of list
Pagination
// Pagination in controller $scope.currentPage = 0; $scope.pageSize = 75; $scope.setCurrentPage = function(currentPage) { $scope.currentPage = currentPage; } $scope.getNumberAsArray = function (num) { return new Array(num); }; $scope.numberOfPages = function() { return Math.ceil($scope.displayedItemsList.length/ $scope.pageSize); }; // Start from filter angular.module('app').filter('startFrom', function() { return function(input, start) { return input.slice(start); }; // Use in HTML // Pagination buttons <button ng-repeat="i in getNumberAsArray(numberOfPages()) track by $index" ng-click="setCurrentPage($index)">{{$index + 1}}</button // Displayed list <tr ng-repeat="item in displayedItemsList | startFrom: currentPage * pageSize | limitTo:pageSize" /tr>
Infinite scrolling
Tuning guidelines
1. Render the list without data binding
2. Do not use a inline method call for calculating the data
<li ng-repeat="item in filteredItems()"> // Bad idea, since very often evaluated. <li ng-repeat="item in items"> // Way to go!
3. Use two lists (one for the view to display, one as data source)
/* Controller */ // Basic list var items = [{name:"John", active:true }, {name:"Adam"}, {name:"Chris"}, {name:"Heather"}]; // Init displayedList $scope.displayedItems = items; // Filter Cache var filteredLists['active'] = $filter('filter)(items, {"active" : true}); // Apply the filter $scope.applyFilter = function(type) { if (filteredLists.hasOwnProperty(type){ // Check if filter is cached $scope.displayedItems = filteredLists[type]; } else { /* Non cached filtering */ } } // Reset filter $scope.resetFilter = function() { $scope.displayedItems = items; } /* View */ <button ng-click="applyFilter('active')">Select active</button> <ul><li ng-repeat="item in displayedItems">{{item.name}}<li></ul>
4. Use ng-if instead of ng-show for additional templates
<li ng-repeat="item in items"> <p> {{ item.title }} </p> <button ng-click="item.showDetails = !item.showDetails">Show details</buttons>
5. Do not use AngularJS directives ng-mouseenter, ng-mouseleave, etc.
6. Tuning hint for filtering: Hide elements with ng-show that are excluded
-
One way to trigger ng-show is to use the expression syntax. The ng-show value is evaluated by using the build in filter syntax.
See also the following plunkr example <input ng-model="query"></input> <li ng-repeat="item in items" ng-show="([item.name] | filter:query).length">{{item.name}}</li> -
Another way to do that is to use pass in an attribute for the ng-show and do the computation for that value in a separate sub-controller. This way is a bit more complex, yet cleaner way like Ben Nadel suggests in his blog post
7. Tuning hint for filtering: Debounce input
/* Controller */ // Watch the queryInput and debounce the filtering by 350 ms. $scope.$ watch('queryInput', function(newValue, oldValue) { if (newValue === oldValue) { return; } $debounce(applyQuery, 350); }); var applyQuery = function() { $scope.filter.query = $scope.query; }; /* View */ <input ng-model="queryInput"/> <li ng-repeat= item in items | filter:filter.query>{{ item.title }} </li>
Further reading
-
1. Project organization with huge apps: http://briantford.com/blog/huuuuuge-angular-apps.html -
2. Stackoverflow answer of Misko Hevery concerning angular data-binding performance: http://stackoverflow.com/questions/9682092/databinding-in-angularjs/9693933#9693933 -
3. Short article with different approaches increase ng-repeat performance: http://www.williambrownstreet.net/blog/?p=437 -
4. Loading more data on request: http://stackoverflow.com/questions/17348058/how-to-improve-performance-of-ngrepeat-over-a-huge-dataset-angular-js -
5. Good article on using the scope: http://thenittygritty.co/angularjs-pitfalls-using-scopes -
6. AngularJS project for dynamic templates http://onehungrymind.com/angularjs-dynamic-templates -
Rendering without data-binding: https://github.com/Pasvaz/bindonce
Got feedback or ideas?