简体   繁体   中英

AngularJS lazy rendering (not lazy loading views)

Let's say I have a lot (3000+) of items I want to render (in a ng-repeat ) in a div with a fixed height and overflow: auto , so I'd get N visible items and a scrollbar for the rest of them.

I'm guessing doing a simple ng-repeat with so many items will probably take a lot of time. Is there a way I can make AngularJS render only those visible N items?

Edit:

An infinite scroll is not what I want. I want the user to be able to scroll to any point of the list, so I literally want a text editor-like behavior. Said with other words: I'd like the scroll to contain the "height" of all the items, but place in the DOM just a few ones.

This answer provides an approach for lazy-rendering only items currently in-view, as defined by the edit to the original question. I want the user to be able to scroll to any point of the list, so I literally want a text editor-like behavior. Said with other words: I'd like the scroll to contain the "height" of all the items, but place in the DOM just a few ones.

Install the angular-inview plugin before trying this.

In order to get your scrollheight you'd need something holding the space for your array items. So I'd start with an array of 3000 simple items (or combine with infinite scroll to whatever extent you want.)

var $app = angular.module('app', ['infinite-scroll']);

$app.controller('listingController', function($scope, $window) {
  $scope.listOfItems = new Array($window._bootstrappedData.length);
  $scope.loadItem = function($index,$inview) {
    if($inview) {
      $scope.listOfItems[$index] = $window._bootstrappedData[$index];
    }
  }
});

Since we're talking about flexible heights, I would create a placeholder for what your content looks like pre-render.

<div ng-controller="listingController">
  <ul>
    <li ng-repeat="item in listOfItems track by $index" in-view="loadItem($index,$inview)" style="min-height:100px"><div ng-if="item">{{item.name}}</div></li>
  </ul>
</div>

Using ng-if will prevent rendering logic from being run unnecessarily. When you scroll an item into view, it'll automatically display. If you want to wait a second to see if the user is still scrolling you could set a timeout in the loadItem method that cancels if the same index gets pushed out of view within a reasonable time period.

Note: If you truly wanted to avoid putting anything in the DOM, you could set your scrollable area to a specific multiple of your "placeholder" height. Then you could create a directive that uses that height to determine the indexes of the items that should be displayed. As soon as you display new items, you'd need to add their heights to the total and make sure you position them at the right spot and make sure your directive knows how to interpret those heights into evaluating the next set of displayed elements. But I think that's way too radical and unnecessary.

Expanding on Grundy's point of using .slice() .

I use ngInfiniteScroll when I need to lazy-render/lazy-load data.

I would keep those 3000 records out of your scope to prevent weighing down your digest performance unnecessarily and then append them to your scope data as you need them. Here's an example.

var $app = angular.module('app', ['infinite-scroll']);

$app.controller('listingController', function($scope, $window) {

  /*
   * Outside of this controller you should bootstrap your data to a non-digested array.
   * If you're loading the data via Ajax, save your data similarly.
   * For example:
   * <script>
   *   window._bootstrappedData = [{id:1,name:'foo'},{id:2,name:'bar'},...];
   * </script>
   */
  var currentPage, pageLength;
  $scope.listOfItems = [];
  currentPage = 0;
  pageLength = 100;
  $scope.nextPage = function() {
    // make sure we don't keep trying to slice data that doesn't exist.
    if (currentPage * pageLength >= $window._bootstrappedData.length) {
      return false;
    }

    // append the next data set to your array
    $scope.listOfItems.push($window._bootstrappedData.slice(currentPage * pageLength, (currentPage + 1) * pageLength));
    currentPage++;
  };

  /*
   * Kickstart this data with our first page.
   */
  return $scope.nextPage();
});

And your template:

<div ng-controller="listingController" infinite-scroll="nextPage()" infinite-scroll-distance="3">
  <ul>
    <li ng-repeat="item in listOfItems">{{item.name}}</li>
  </ul>
</div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM