AngularJS infinite scroll (ng-repeat) - remove top elements from the DOM

I have ng-repeat which loads thousands of records with some complexity that can have heights between 100px and 1200px. Needless to say, performance is becoming quite a hit.

The infinite scroll module will work very well, in most cases, until you hit the edge where you scroll down to the bottom and most of the elements are loaded into the DOM, which brings me back to the square.

Angular-vs-repeat would be ideal for my case, but I did not understand how to calculate each next element height, as they are "Not fixed.

Which brings me back to endless scrolling. I assume that if the top elements (above the viewport) are replaced by an empty DIV with a calculated height equal to their total sum of height, performance will not be a problem. While scrolling will bring them back to dom and subtract the empty height of the DIV.

Has anyone done this before? Any suggestions? The code snippets would be great.

+5
source share
1 answer

ng-repeat has pretty cool performance with long lists due to the overhead associated with bindings. One specialized library that I especially like is ag-grid , which is a convenient example with a variable row height. You could see if this would work for your purposes.

If none of this fits your needs for this, you can always flip your own directive and handle the DOM manipulations yourself, as a piece of code that I have compiled below. It does not cover everything that you mentioned, but includes infinite scrolling and removes old elements, replacing their height with a blank <div> , without using ng-repeat.

 angular.module('SuperList', []) .controller('mainCtrl', ['$scope', '$compile', function($scope, $compile) { // Magic numbers var itemsPerLoad = 4; var thresholdPx = 1200; var removeThresholdPx = 1600; // Options to control your directive are cool $scope.listOptions = { items: [], renderer: renderer, threshold: thresholdPx, removeThreshold: removeThresholdPx, loadFn: loadNewItems }; // This function creates a div for each item in our dataset whenever // it called by the directive function renderer(item) { var itemElem = angular.element('<div></div'); itemElem.css('height', item.height + 'px'); itemElem.html(item.text); return itemElem; // If each row needs special angular behavior, you can compile it with // something like the following instead of returning basic html // return $compile(itemElem)($scope); } // This gets called by the directive when we need to populate more items function loadNewItems() { // Let do it async like we're getting something from the server setTimeout(function() { for (var i = 0; i < itemsPerLoad; i++) { // Give each item random text and height $scope.listOptions.items.push({ text: Math.random().toString(36).substr(2, Infinity), height: Math.floor(100 + Math.random() * 1100) }); } // Call the refresh function to let the directive know we've loaded // We could, of course, use $watch in the directive and just make // sure a $digest gets called here, but doing it this way is much faster. $scope.listOptions.api.refresh(); }, 500); // return true to let the directive know we're waiting on data, so don't // call this function again until that happens return true; } } ]) .directive('itemList', function() { return { restrict: 'A', scope: { itemList: '=' }, link: function(scope, element, attrs) { var el = element[0]; var emptySpace = angular.element('<div class="empty-space"></div>'); element.append(emptySpace); // Keep a selection of previous elements so we can remove them // if the user scrolls far enough var prevElems = null; var prevHeight = 0; var nextElems = 0; var nextHeight = 0; // Options are defined above the directive to keep things modular var options = scope.itemList; // Keep track of how many rows we've rendered so we know where we left off var renderedRows = 0; var pendingLoad = false; // Add some API functions to let the calling scope interact // with the directive more effectively options.api = { refresh: refresh }; element.on('scroll', checkScroll); // Perform the initial setup refresh(); function refresh() { addRows(); checkScroll(); } // Adds any rows that haven't already been rendered. Note that the // directive does not process any removed items, so if that functionality // is needed you'll need to make changes to this directive function addRows() { nextElems = []; for (var i = renderedRows; i < options.items.length; i++) { var e = options.renderer(options.items[i]); nextElems.push(e[0]) element.append(e); renderedRows++; pendingLoad = false; } nextElems = angular.element(nextElems); nextHeight = el.scrollHeight; // Do this for the first time to initialize if (!prevElems && nextElems.length) { prevElems = nextElems; prevHeight = nextHeight; } } function checkScroll() { // Only check if we need to load if there isn't already an async load pending if (!pendingLoad) { if ((el.scrollHeight - el.scrollTop - el.clientHeight) < options.threshold) { console.log('Loading new items!'); pendingLoad = options.loadFn(); // If we're not waiting for an async event, render the new rows if (!pendingLoad) { addRows(); } } } // if we're past the remove threshld, remove all previous elements and replace // lengthen the empty space div to fill the space they occupied if (options.removeThreshold && el.scrollTop > prevHeight + options.removeThreshold) { console.log('Removing previous elements'); prevElems.remove(); emptySpace.css('height', prevHeight + 'px'); // Stage the next elements for removal prevElems = nextElems; prevHeight = nextHeight; } } } }; }); 
 .item-list { border: 1px solid green; width: 600px; height: 300px; overflow: auto; } .item-list > div { border: 1px solid blue; } .item-list > .empty-space { background: #aaffaa; } 
 <html> <head> <link rel="stylesheet" href="test.css"> </head> <body ng-app="SuperList" ng-controller="mainCtrl"> <div class="item-list" item-list="listOptions"></div> <script src="https://opensource.keycdn.com/angularjs/1.5.8/angular.min.js"></script> <script src="test.js"></script> </body> </html> 
0
source

Source: https://habr.com/ru/post/1263104/


All Articles