如何在庞大的数据集(angular.js)上提高ngRepeat的性能?


165

我有一个巨大的数据集,其中包含数千行,每个行有大约10个字段,大约2MB的数据。我需要在浏览器中显示它。最简单的方法(获取数据,将其放入$scopeng-repeat=""完成工作)可以很好地工作,但是当它开始将节点插入DOM时,它会使浏览器冻结大约半分钟。我应该如何解决这个问题?

一种选择是将行逐行追加,$scope并等待ngRepeat完成向DOM中插入一个块后再移至下一个。但是AFAIK ngRepeat在完成“重复”操作时不会返回报告,因此会很丑陋。

另一种选择是将服务器上的数据拆分为页面,然后在多个请求中获取它们,但这更加丑陋。

我浏览了Angular文档以查找类似的内容ng-repeat="data in dataset" ng-repeat-steps="500",但未找到任何内容。我对Angular的方法还很陌生,所以有可能我完全没有注意这一点。最佳做法是什么?


10
您真的要显示所有行吗?只显示用户可以看到的那么多行呢?例如,您可以limitTo用来仅显示20个项目:<p ng-repeat="data in dataset | limitTo:20">{{data}}</p>仅显示20个项目。然后,您可以使用页面并显示接下来的10个项目或类似内容。:)
AndreM96 2013年

对于“完成时重复返回”的事情,除了ng-repeat之外,您还可以使用自定义指令。(见这里所选答案) stackoverflow.com/questions/13471129/...
mayankcpdixit

参考这个问题一定会对您有所帮助。:[此处输入链接的描述] [1] [1] stackoverflow.com/questions/25481021/...
马赫什

Answers:


159

我同意@ AndreM96的观点,最好的方法是仅显示有限数量的行,更快更好的UX,这可以通过分页或无限滚动来完成。

使用limitTo过滤器,使用Angular进行无限滚动非常简单。您只需要设置初始限制,当用户要求提供更多数据时(为简便起见,我使用按钮)就可以增加限制。

<table>
    <tr ng-repeat="d in data | limitTo:totalDisplayed"><td>{{d}}</td></tr>
</table>
<button class="btn" ng-click="loadMore()">Load more</button>

//the controller
$scope.totalDisplayed = 20;

$scope.loadMore = function () {
  $scope.totalDisplayed += 20;  
};

$scope.data = data;

这是一个JsBin

这种方法对手机可能是个问题,因为通常在滚动大量数据时它们会滞后,因此在这种情况下,我认为分页更为合适。

为此,您将需要limitTo过滤器和自定义过滤器来定义所显示数据的起点。

这是带有分页的JSBin


不错的选择!!!您知道我必须显示所有项目时可以使用的任何方法。任何加载符号,或者一个插入DOM之后的符号?
mayankcpdixit 2014年

您的意思是在获取数据时显示“正在加载...”或其他内容吗?
Bertrand 2014年

1
@Sumit limitTo将应用于ng-repeat范围,因此结果将是一个新数组,该数组将传递给ng-repeat,您的数据数组仍然相同,并且您仍然可以搜索所有内容。
Bertrand

12
如果用户按10次以上加载,并且每次按100次以上添加项,那么如何提高性能?
hariszaman 2015年

5
@hariszaman我同意。这不会提高性能。这只会延迟不良的性能。除非您将其虚拟化(否则ui-grid会这样做),否则无限滚动会给您带来麻烦。
理查德

41

Ionic的collectionRepeat指令和类似的其他实现方式体现了最热且可以说是最具扩展性的方法来克服大型数据集的这些挑战。一个比较好的术语是“遮挡剔除”,但您可以将其总结为:不要仅仅将渲染的DOM元素的数量限制为任意(但仍然很高)的分页数,例如50、100、500 ... ,仅限制为用户可以看到的尽可能多的元素

如果您执行类似“无限滚动”的操作,则可以减少初始 DOM计数,但是经过几次刷新后它会很快膨胀,因为所有这些新元素都位于底部。滚动成为爬网,因为滚动全部与元素计数有关。没有什么是无限的。

collectionRepeat方法是仅使用视口中适合的尽可能多的元素,然后对其进行回收。当一个元素旋转出视线时,它会与渲染树分离,重新填充列表中新项目的数据,然后重新附加到列表另一端的渲染树。这是人类已知的最快方法,它利用有限的现有元素集来获取和接收DOM中的新信息,而不是传统的创建/销毁...创建/销毁周期。使用这种方法,您可以真正实现无限滚动。

请注意,您不必使用Ionic即可使用/ hack / adapt collectionRepeat或类似的任何其他工具。这就是为什么他们称其为开源。:-)(也就是说,Ionic团队正在做一些非常巧妙的事情,值得您注意。)


至少有一个出色的示例,可以在React中做非常类似的事情。您只是选择不对树中未显示的任何内容进行渲染,而不是用更新的内容来回收元素。尽管它们非常简单的POC实施允许一些闪烁,但它可以快速处理5000个项目。


此外... track by即使在数据集较小的情况下,使用也是非常有用的,以呼应其他一些帖子。认为它是强制性的。


Ionic团队的绝佳想法。我想知道这是否来自本机视图的呈现方式?
布拉德利洪水

例如,iOS中的UITableView使用相同的方法来呈现大型数据集。我认为这是许多本机视图中常用的方法。
德米特里·科登科

36

我建议看一下:

优化AngularJS:1200ms至35ms

他们通过优化ng-repeat的4个部分来制定新的指令:

优化#1:缓存DOM元素

优化#2:聚集观察者

优化#3:延迟元素创建

Optimization#4:绕过监视程序以查找隐藏的元素

该项目在github上:

用法:

1-在您的单页应用程序中包含以下文件:

  • core.js
  • scalyr.js
  • slyEvaluate.js
  • slyRepeat.js

2-添加模块依赖性:

var app = angular.module("app", ['sly']);

3-替换ng-repeat

<tr sly-repeat="m in rows"> .....<tr>

请享用!


4
我认为scalyr.js已经包含其他文件。因为是构建脚本的结果。
dnocode '16

我尝试使用Scalyr,但过滤器无法正常工作。<tr sly-repeat =“在main.customers中的选项|过滤器:search_input | limitTo:20”>
aldesabido

这非常有帮助。我在AngularJS 1.6应用程序上使用它,客户端希望看到大量的数据单元(通常我设计的页面具有分页/精简数据元素,但客户端需要一次比较大量数据)。到目前为止,由于有了这个库,单元格的网格已从无法使用变为完美。但是这个库是在AngularJS 1.2天内编写的,因此我将仔细测试以查找问题。
2013年

根据我目前的判断,gatedScope.js文件(323行)是唯一需要验证才能在最新版本的AngularJS上运行的文件。这个拉取请求值得注意:github.com/karser/angular/commit/…。它更新签名rootScope。$ new。
2013年

包括所有四个js文件并已使用,sly-repeat但没有任何帮助,结果仍然很慢,浏览器滞后也越来越违规[Violation] 'setTimeout' handler took 54ms[Violation] 'scroll' handler took 1298ms
Gaurav Aggarwal

15

除了上述所有提示(例如逐个循环和较小的循环)之外,这一提示也对我有很大帮助

<span ng-bind="::stock.name"></span>

这段代码会在名称加载后立即显示该名称,并在此之后停止监视。同样,对于ng-repeats,它可以用作

<div ng-repeat="stock in ::ctrl.stocks">{{::stock.name}}</div>

但是它仅适用于AngularJS 1.3版或更高版本。来自 http://www.befundoo.com/blog/optimizing-ng-repeat-in-angularjs/


您是否需要::重复和表达?文档说不,但是我不确定测试此方法是否有效的方法。docs.angularjs.org/guide/expression
Crhistian Ramirez

12

您可以使用“跟踪依据”来提高效果:

<div ng-repeat="a in arr track by a.trackingKey">

比...快:

<div ng-repeat="a in arr">

参考:https : //www.airpair.com/angularjs/posts/angularjs-performance-large-applications


1
这实际上对性能没有帮助。参见jsperf.com/ng-repeat-vs-ng-repeat-with-trace-by-id
ilter 2015年

使用track by时,您不会在每次获取新数据时都从头开始跟踪数组元素。结果,这提高了性能。
user1920302

2
仅当ng-repeat中的数据更改时,此功能才有用。对于初始负载,它可能不会提高性能。
Sumesh Kuttan


9

规则一:切勿让用户等待任何东西。

要记住,一个需要10秒的生命增长页面的显示速度要比在黑屏之前等待3秒钟并立即获得全部的速度快得多。

因此,而不是化妆的页面速度快,只是让页面显得要快,即使最后的结果是慢:

function applyItemlist(items){
    var item = items.shift();
    if(item){
        $timeout(function(){
            $scope.items.push(item);
            applyItemlist(items);
        }, 0); // <-- try a little gap of 10ms
    }
}

上面的代码使列表看起来逐行增长,并且总是比一次渲染慢。但是对于用户来说,它似乎更快。


如何在HTML页面中使用此功能?
Antonis

9

虚拟滚动是处理庞大列表和大型数据集时提高滚动性能的另一种方法。

实现此目的的一种方法是使用Angular Material, md-virtual-repeat如本演示中演示的50,000项所示

直接从虚拟重复文档中获取:

虚拟重复是ng-repeat的一种有限替代,它仅呈现足够多的dom节点以填充容器并在用户滚动时对其进行回收。


2
哇,我认为这是最有趣的答案。这适用于较旧版本的angular吗?(例如1.2版)
Thariq Nugrohotomo,2016年

2
@ThariqNugrohotomo请注意,使用Angular Material需要使用Angular 1.3.x或更高版本。也感谢您的支持,虚拟重复让我也很惊讶,我们已经在显示非常长的结果列表的移动应用程序中使用了它。
Sarantis Tofas

6

另一个版本@Steffomio

无需单独添加每个项目,我们可以按块添加项目。

// chunks function from here: 
// http://stackoverflow.com/questions/8495687/split-array-into-chunks#11764168
var chunks = chunk(folders, 100);

//immediate display of our first set of items
$scope.items = chunks[0];

var delay = 100;
angular.forEach(chunks, function(value, index) {
    delay += 100;

    // skip the first chuck
    if( index > 0 ) {
        $timeout(function() {
            Array.prototype.push.apply($scope.items,value);
        }, delay);
    }       
});

有趣的主意。我在约8000个元素的数组上进行了尝试,虽然它确实使页面最初具有更高的响应速度,但在每个块之后它的响应速度都变得较慢。
保罗·布兰南

在拥有500多个项目之后,这对我的应用程序来说是一个巨大的问题。我建议分页或无限加载。
joalcego

0

有时发生了什么,您在几毫秒内就从服务器(或后端)获得了数据(例如,我假设它是100毫秒),但是要花更多的时间才能在我们的网页上显示(假设要花900毫秒才能完成)显示)。

因此,这里发生的是800毫秒,仅用于渲染网页。

我在Web应用程序中所做的是,我使用了分页(或者也可以使用无限滚动)来显示数据列表。假设我正在显示50个数据/页。

因此,我不会一次加载渲染所有数据,最初只加载50个数据,而这只需要50毫秒(我在这里假设)。

因此,这里的总时间从900毫秒减少到150毫秒,一旦用户请求下一页然后显示下50个数据,依此类推。

希望这可以帮助您提高性能。祝一切顺利


0
Created a directive (ng-repeat with lazy loading) 

当它到达页面底部并删除一半以前加载的数据时加载数据,当它再次到达div顶部时再次加载以前的数据(取决于页码),从而删除当前数据的一半。一次只存在有限的数据,这可能会导致更好的性能,而不是在加载时呈现整个数据。

HTML代码:

<!DOCTYPE html>
<html ng-app="plunker">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
    <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.20/angular.js" data-semver="1.3.20"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="ListController">
  <div class="row customScroll" id="customTable" datafilter pagenumber="pageNumber" data="rowData" searchdata="searchdata" itemsPerPage="{{itemsPerPage}}"  totaldata="totalData"   selectedrow="onRowSelected(row,row.index)"  style="height:300px;overflow-y: auto;padding-top: 5px">

    <!--<div class="col-md-12 col-xs-12 col-sm-12 assign-list" ng-repeat="row in CRGC.rowData track by $index | orderBy:sortField:sortReverse | filter:searchFish">-->
    <div class="col-md-12 col-xs-12 col-sm-12 pdl0 assign-list" style="padding:10px" ng-repeat="row in rowData" ng-hide="row[CRGC.columns[0].id]=='' && row[CRGC.columns[1].id]==''">
        <!--col1-->

        <div ng-click ="onRowSelected(row,row.index)"> <span>{{row["sno"]}}</span> <span>{{row["id"]}}</span> <span>{{row["name"]}}</span></div>
      <!--   <div class="border_opacity"></div> -->
    </div>

</div>

  </body>

</html>

角度代码:

var app = angular.module('plunker', []);
var x;
ListController.$inject = ['$scope', '$timeout', '$q', '$templateCache'];

function ListController($scope, $timeout, $q, $templateCache) {
  $scope.itemsPerPage = 40;
  $scope.lastPage = 0;
  $scope.maxPage = 100;
  $scope.data = [];
  $scope.pageNumber = 0;


  $scope.makeid = function() {
    var text = "";
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    for (var i = 0; i < 5; i++)
      text += possible.charAt(Math.floor(Math.random() * possible.length));

    return text;
  }


  $scope.DataFormFunction = function() {
      var arrayObj = [];
      for (var i = 0; i < $scope.itemsPerPage*$scope.maxPage; i++) {
          arrayObj.push({
              sno: i + 1,
              id: Math.random() * 100,
              name: $scope.makeid()
          });
      }
      $scope.totalData = arrayObj;
      $scope.totalData = $scope.totalData.filter(function(a,i){ a.index = i; return true; })
      $scope.rowData = $scope.totalData.slice(0, $scope.itemsperpage);
    }
  $scope.DataFormFunction();

  $scope.onRowSelected = function(row,index){
    console.log(row,index);
  }

}

angular.module('plunker').controller('ListController', ListController).directive('datafilter', function($compile) {
  return {
    restrict: 'EAC',
    scope: {
      data: '=',
      totalData: '=totaldata',
      pageNumber: '=pagenumber',
      searchdata: '=',
      defaultinput: '=',
      selectedrow: '&',
      filterflag: '=',
      totalFilterData: '='
    },
    link: function(scope, elem, attr) {
      //scope.pageNumber = 0;
      var tempData = angular.copy(scope.totalData);
      scope.totalPageLength = Math.ceil(scope.totalData.length / +attr.itemsperpage);
      console.log(scope.totalData);
      scope.data = scope.totalData.slice(0, attr.itemsperpage);
      elem.on('scroll', function(event) {
        event.preventDefault();
      //  var scrollHeight = angular.element('#customTable').scrollTop();
      var scrollHeight = document.getElementById("customTable").scrollTop
        /*if(scope.filterflag && scope.pageNumber != 0){
        scope.data = scope.totalFilterData;
        scope.pageNumber = 0;
        angular.element('#customTable').scrollTop(0);
        }*/
        if (scrollHeight < 100) {
          if (!scope.filterflag) {
            scope.scrollUp();
          }
        }
        if (angular.element(this).scrollTop() + angular.element(this).innerHeight() >= angular.element(this)[0].scrollHeight) {
          console.log("scroll bottom reached");
          if (!scope.filterflag) {
            scope.scrollDown();
          }
        }
        scope.$apply(scope.data);

      });

      /*
       * Scroll down data append function
       */
      scope.scrollDown = function() {
          if (scope.defaultinput == undefined || scope.defaultinput == "") { //filter data append condition on scroll
            scope.totalDataCompare = scope.totalData;
          } else {
            scope.totalDataCompare = scope.totalFilterData;
          }
          scope.totalPageLength = Math.ceil(scope.totalDataCompare.length / +attr.itemsperpage);
          if (scope.pageNumber < scope.totalPageLength - 1) {
            scope.pageNumber++;
            scope.lastaddedData = scope.totalDataCompare.slice(scope.pageNumber * attr.itemsperpage, (+attr.itemsperpage) + (+scope.pageNumber * attr.itemsperpage));
            scope.data = scope.totalDataCompare.slice(scope.pageNumber * attr.itemsperpage - 0.5 * (+attr.itemsperpage), scope.pageNumber * attr.itemsperpage);
            scope.data = scope.data.concat(scope.lastaddedData);
            scope.$apply(scope.data);
            if (scope.pageNumber < scope.totalPageLength) {
              var divHeight = $('.assign-list').outerHeight();
              if (!scope.moveToPositionFlag) {
                angular.element('#customTable').scrollTop(divHeight * 0.5 * (+attr.itemsperpage));
              } else {
                scope.moveToPositionFlag = false;
              }
            }


          }
        }
        /*
         * Scroll up data append function
         */
      scope.scrollUp = function() {
          if (scope.defaultinput == undefined || scope.defaultinput == "") { //filter data append condition on scroll
            scope.totalDataCompare = scope.totalData;
          } else {
            scope.totalDataCompare = scope.totalFilterData;
          }
          scope.totalPageLength = Math.ceil(scope.totalDataCompare.length / +attr.itemsperpage);
          if (scope.pageNumber > 0) {
            this.positionData = scope.data[0];
            scope.data = scope.totalDataCompare.slice(scope.pageNumber * attr.itemsperpage - 0.5 * (+attr.itemsperpage), scope.pageNumber * attr.itemsperpage);
            var position = +attr.itemsperpage * scope.pageNumber - 1.5 * (+attr.itemsperpage);
            if (position < 0) {
              position = 0;
            }
            scope.TopAddData = scope.totalDataCompare.slice(position, (+attr.itemsperpage) + position);
            scope.pageNumber--;
            var divHeight = $('.assign-list').outerHeight();
            if (position != 0) {
              scope.data = scope.TopAddData.concat(scope.data);
              scope.$apply(scope.data);
              angular.element('#customTable').scrollTop(divHeight * 1 * (+attr.itemsperpage));
            } else {
              scope.data = scope.TopAddData;
              scope.$apply(scope.data);
              angular.element('#customTable').scrollTop(divHeight * 0.5 * (+attr.itemsperpage));
            }
          }
        }
    }
  };
});

指令演示

Another Solution: If you using UI-grid in the project then  same implementation is there in UI grid with infinite-scroll.

根据分区的高度,它会加载数据,并在滚动时会添加新数据,并删除先前的数据。

HTML代码:

<!DOCTYPE html>
<html ng-app="plunker">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <link rel="stylesheet" href="https://cdn.rawgit.com/angular-ui/bower-ui-grid/master/ui-grid.min.css" type="text/css" />
    <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.20/angular.js" data-semver="1.3.20"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-grid/4.0.6/ui-grid.js"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="ListController">
     <div class="input-group" style="margin-bottom: 15px">
      <div class="input-group-btn">
        <button class='btn btn-primary' ng-click="resetList()">RESET</button>
      </div>
      <input class="form-control" ng-model="search" ng-change="abc()">
    </div>

    <div data-ui-grid="gridOptions" class="grid" ui-grid-selection  data-ui-grid-infinite-scroll style="height :400px"></div>

    <button ng-click="getProductList()">Submit</button>
  </body>

</html>

角度代码:

var app = angular.module('plunker', ['ui.grid', 'ui.grid.infiniteScroll', 'ui.grid.selection']);
var x;
angular.module('plunker').controller('ListController', ListController);
ListController.$inject = ['$scope', '$timeout', '$q', '$templateCache'];

function ListController($scope, $timeout, $q, $templateCache) {
    $scope.itemsPerPage = 200;
    $scope.lastPage = 0;
    $scope.maxPage = 5;
    $scope.data = [];

    var request = {
        "startAt": "1",
        "noOfRecords": $scope.itemsPerPage
    };
    $templateCache.put('ui-grid/selectionRowHeaderButtons',
        "<div class=\"ui-grid-selection-row-header-buttons \" ng-class=\"{'ui-grid-row-selected': row.isSelected}\" ><input style=\"margin: 0; vertical-align: middle\" type=\"checkbox\" ng-model=\"row.isSelected\" ng-click=\"row.isSelected=!row.isSelected;selectButtonClick(row, $event)\">&nbsp;</div>"
    );


    $templateCache.put('ui-grid/selectionSelectAllButtons',
        "<div class=\"ui-grid-selection-row-header-buttons \" ng-class=\"{'ui-grid-all-selected': grid.selection.selectAll}\" ng-if=\"grid.options.enableSelectAll\"><input style=\"margin: 0; vertical-align: middle\" type=\"checkbox\" ng-model=\"grid.selection.selectAll\" ng-click=\"grid.selection.selectAll=!grid.selection.selectAll;headerButtonClick($event)\"></div>"
    );

    $scope.gridOptions = {
        infiniteScrollDown: true,
        enableSorting: false,
        enableRowSelection: true,
        enableSelectAll: true,
        //enableFullRowSelection: true,
        columnDefs: [{
            field: 'sno',
            name: 'sno'
        }, {
            field: 'id',
            name: 'ID'
        }, {
            field: 'name',
            name: 'My Name'
        }],
        data: 'data',
        onRegisterApi: function(gridApi) {
            gridApi.infiniteScroll.on.needLoadMoreData($scope, $scope.loadMoreData);
            $scope.gridApi = gridApi;
        }
    };
    $scope.gridOptions.multiSelect = true;
    $scope.makeid = function() {
        var text = "";
        var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

        for (var i = 0; i < 5; i++)
            text += possible.charAt(Math.floor(Math.random() * possible.length));

        return text;
    }
    $scope.abc = function() {
        var a = $scope.search;
        x = $scope.searchData;
        $scope.data = x.filter(function(arr, y) {
            return arr.name.indexOf(a) > -1
        })
        console.log($scope.data);
        if ($scope.gridApi.grid.selection.selectAll)
            $timeout(function() {
                $scope.gridApi.selection.selectAllRows();
            }, 100);
    }


    $scope.loadMoreData = function() {
        var promise = $q.defer();
        if ($scope.lastPage < $scope.maxPage) {
            $timeout(function() {
                var arrayObj = [];
                for (var i = 0; i < $scope.itemsPerPage; i++) {
                    arrayObj.push({
                        sno: i + 1,
                        id: Math.random() * 100,
                        name: $scope.makeid()
                    });
                }

                if (!$scope.search) {
                    $scope.lastPage++;
                    $scope.data = $scope.data.concat(arrayObj);
                    $scope.gridApi.infiniteScroll.dataLoaded();
                    console.log($scope.data);
                    $scope.searchData = $scope.data;
                    // $scope.data = $scope.searchData;
                    promise.resolve();
                    if ($scope.gridApi.grid.selection.selectAll)
                        $timeout(function() {
                            $scope.gridApi.selection.selectAllRows();
                        }, 100);
                }


            }, Math.random() * 1000);
        } else {
            $scope.gridApi.infiniteScroll.dataLoaded();
            promise.resolve();
        }
        return promise.promise;
    };

    $scope.loadMoreData();

    $scope.getProductList = function() {

        if ($scope.gridApi.selection.getSelectedRows().length > 0) {
            $scope.gridOptions.data = $scope.resultSimulatedData;
            $scope.mySelectedRows = $scope.gridApi.selection.getSelectedRows(); //<--Property undefined error here
            console.log($scope.mySelectedRows);
            //alert('Selected Row: ' + $scope.mySelectedRows[0].id + ', ' + $scope.mySelectedRows[0].name + '.');
        } else {
            alert('Select a row first');
        }
    }
    $scope.getSelectedRows = function() {
        $scope.mySelectedRows = $scope.gridApi.selection.getSelectedRows();
    }
    $scope.headerButtonClick = function() {

        $scope.selectAll = $scope.grid.selection.selectAll;

    }
}

带有无限滚动演示的UI网格演示


欢迎使用指向解决方案的链接,但是请确保没有该链接的情况下,您的回答是有用的:在链接周围添加上下文,以便您的其他用户可以了解它的含义和含义,然后引用该页面中最相关的部分如果目标页面不可用,请重新链接。只是链接的答案可能会被删除
SᴀᴍOnᴇᴌᴀ

-2

对于大数据集和多个值下降的情况,最好使用ng-options而不是ng-repeat

ng-repeat之所以慢,是因为它会遍历所有即将到来的值,而ng-options只是显示为选择选项。

ng-options='state.StateCode as state.StateName for state in States'>

比快得多

<option ng-repeat="state in States" value="{{state.StateCode}}">
    {{state.StateName }}
</option>

您是否检查了ng-options的性能?我试图优化我的代码,但没有帮助。速度与ng-repeat相同。-1
Icet

仅适用于select,ng-repeat功能更强大。但是,ng-Options的速度确实比ng-repeat快。AngularJs docs提到了2000项差异:docs.angularjs.org/api/ng/directive/select
kaiser
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.