AngularJS、ng-repeat、复选框和 toggleAll

AngularJS, ng-repeat, checkboxes and toggleAll

毫无疑问。通过互联网搜索,您可以轻松找到有关 ng-repeat 和复选框如何工作的示例。所有这些示例都只包含几个复选框。但是,您是否尝试过创建数百个复选框,然后使用一些切换按钮 check/uncheck 所有复选框?该应用程序变得完全没有响应。在浏览器中还可以,但在设备(iPad4、iPad mini 等)上测试应用程序时,应用程序完全没有响应。

我在这里创建了一个 Plunker 示例:http://plnkr.co/edit/wfa3TIp3BYaPvzX8ehAf?p=preview

尝试使用至少 500 个条目测试切换复选框,以便您可以看到延迟。现在的问题是,有什么方法可以提高性能吗?检查时间轴的记录,这是我得到的 500 个条目的结果 (:

19.131 ms Scripting
150.104 ms Rendering
55.543 ms Painting
138.402 ms Other
2.95 s Idle

如您所见,渲染需要花费宝贵的时间,我们不能浪费这样的时间。

HTML:

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

  <head>
  <meta charset="utf-8" />
  <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width" />
  <title>Ionic Framework Example</title>
  <link href="//code.ionicframework.com/nightly/css/ionic.css" rel="stylesheet"/>
  <link href="index.css" rel="stylesheet"/>
  <script src="//code.ionicframework.com/nightly/js/ionic.bundle.js"></script>
    <script src="script.js"></script>
  </head>

  <body>
<ion-view view-title="main module">
  <ion-content ng-controller="StartCtrl">
    <div class="list">
      <div class="item item-divider">
        Options
      </div>

      <a class="item item-icon-left" href="#" ng-click="generateEntries()">
        <i class="icon ion-plus-circled"></i>
        Add more entries
      </a>

      <a class="item item-icon-left" href="#" ng-click="clearEntries()">
        <i class="icon ion-trash-b"></i>
        Clear entries
      </a>
      <div class="item item-icon-left" href="#">
        <i class="icon ion-person-stalker"></i>
        Entries in array
        <span class="badge badge-assertive">{{entries.length}}</span>
      </div>
      <li class="item item-checkbox">
        <label class="checkbox">
          <input type="checkbox" ng-model="checkedAll" ng-click="toggleAll()">
        </label>
        Toggle all
      </li>
      <div class="item item-divider">
        Entries
      </div>
      <li class="item item-checkbox" ng-repeat="entry in entries track by $index">
        <label class="checkbox">
          <input type="checkbox" ng-model="entry.checked" name="entry.id">
        </label>
        {{entry.id}} - {{entry.name}}
        <span class="badge badge-light">{{$index}}</span>
      </li>
    </div>
  </ion-content>
</ion-view>

  </body>

</html>

JS:

// Code goes here
var app = angular.module('myApp', []);

app.controller('StartCtrl', function ($scope) {

  // bind data from service
  // this.someData = Start.someData;
  $scope.entries = [];

  $scope.generateEntries = function () {
    var names = ['Mark', 'John', 'Maria', 'Lea', 'Marco'];
    var obj = {};
    for (var i = 0; i < 50; i++) {
      obj = {'id': Math.floor(Math.random() * 10000), 'name': names[Math.floor(Math.random() * names.length)]};
      $scope.entries.push(obj);
    }
  };

  $scope.clearEntries = function () {
    $scope.entries = [];
  };

  $scope.toggleAll = function () {
    for (var i = 0; i < $scope.entries.length; i++) {
      $scope.entries[i].checked = $scope.checkedAll;
    }
  };
});

感谢所有参与本次讨论的人。

使用 entry in entries track by entry.id 而不是 $index。有了 500 个条目,这对我来说提高了很多统计数据:

使用track by $index

65.660 ms Scripting
246.985 ms Rendering
129.748 ms Painting
1.23 s Other
3.31 s Idle

使用track by entry.id

46.534 ms Scripting
30.827 ms Rendering
17.631 ms Painting
226.515 ms Other
3.18 s Idle

@Numyx 和@jaycp 的回复都非常有帮助。我对代码做了一些改进: 1. 我正在使用 track by entry.id 而不是 track by $index,这加快了渲染速度,正如@Numyx 所说, 2. 我使用了 ion-infinite-scroll 所以我不会一次加载所有结果(例如 5000),但只加载大约。 25.滚动到底部加载更多。 3. 我正在使用 2 个数据集。 1 个用于所有条目,1 个用于视图条目。当我们滚动时,View Entries 数组被填充。我们滚动得越多,来自 ALL 数组的条目就越多被添加到视图数组中, 4. 我使用 $timeout 来显示 $ionicLoading 并在渲染完成时隐藏 $ionicLoading,

因此,当使用切换按钮时,现在我们不会选中所有条目,而只会选中可见数组中的条目。

这是更新后的代码:

start.html

<ion-view view-title="main module">
  <ion-content>
    <div class="list">
      <div class="item item-divider">
        Options
      </div>

      <a class="item item-icon-left" href="#" ng-click="start.generateEntries()">
        <i class="icon ion-plus-circled"></i>
        Add more entries
      </a>

      <a class="item item-icon-left" href="#" ng-click="start.clearEntries()">
        <i class="icon ion-trash-b"></i>
        Clear entries
      </a>
      <div class="item item-icon-left" href="#">
        <i class="icon ion-person-stalker"></i>
        Entries in array
        <span class="badge badge-assertive">{{start.entries.length}}</span>
      </div>
      <li class="item item-checkbox">
        <label class="checkbox">
          <input type="checkbox" ng-model="start.checkedAll" ng-click="start.toggleAll()">
        </label>
        Toggle all
      </li>
      <div class="item item-divider">
        Entries {{start.entriesView.length}}
      </div>
      <li class="item item-checkbox" ng-repeat="entry in start.entriesView track by entry.id">
        <label class="checkbox">
          <input type="checkbox" ng-model="entry.checked" name="entry.id">
        </label>
        {{entry.id}} - {{entry.name}}
        <span class="badge badge-light">{{$index}}</span>
      </li>
      <ion-infinite-scroll
      on-infinite="start.loadMore()"
      ng-if="start.canLoadMore()"
      immediate-check="false"
      distance="1%">
      </ion-infinite-scroll>
    </div>
  </ion-content>
</ion-view>

开始-ctrl.js

'use strict';
angular.module('main')
.controller('StartCtrl', function (Utility, $timeout, $scope) {

  // bind data from service
  // this.someData = Start.someData;
  this.entries = [];
  this.entriesView = [];
  this.numEntriesToCreate = 5000;
  this.numEntriesToAdd = 25;
  var self = this;

  $scope.$on('$stateChangeSuccess', function () {
    console.log('START');
    self.loadMore();
  });

  /**
   * generate more entries
   */
  this.generateEntries = function () {
    var names = ['Gregor', 'Mathias', 'Roland', 'Jonas', 'Marco'];
    var obj = {};

    for (var i = 0; i < self.numEntriesToCreate; i++) {
      obj = {'id': Math.random() + Math.random() * 10000, 'name': names[Math.floor(Math.random() * names.length)]};
      self.entries.push(obj);
    }
  };

  this.clearEntries = function () {
    self.entries = [];
  };

  this.toggleAll = function () {
    self.startLoading();

    console.log(self.checkedAll);

    // we wait for spinner to appear (500ms), then start..
    $timeout(function () {
      for (var i = 0; i < self.entriesView.length; i++) {
        self.entriesView[i].checked = self.checkedAll;
      }

      self.finishedLoading();

    }, 500);
  };

  this.loadMore = function () {
    if (self.canLoadMore()) {
      // self.startLoading();
      $timeout(function () {
        self.entriesView = self.entriesView.concat(
          self.entries.slice(self.entriesView.length, self.entriesView.length + self.numEntriesToAdd) // exact items from our original entries
        );

        $scope.$broadcast('scroll.infiniteScrollComplete');
        // self.finishedLoading();
      }, 500);
    }
    //
  };

  this.canLoadMore = function () {
    return (self.entriesView < self.entries) ? true : false;
  };

  this.startLoading = function () {
    Utility.startTimer();
    Utility.showLoading();
  };

  this.finishedLoading = function () {
    $timeout(function () {
      Utility.hideLoading();
      console.log('execution took ' + Utility.endTimer() + 'ms.');
    });
  };

  console.log('init. creating ' + self.numEntriesToCreate + ' entries');
  self.generateEntries();

});

效用-serv.js

'use strict';
angular.module('main')
.service('Utility', function ($ionicLoading) {
  this.opt = {
    startTime: null
  };
  this.showLoading = function () {
    $ionicLoading.show({template: '<ion-spinner></ion-spinner>'});
  };
  this.hideLoading = function () {
    $ionicLoading.hide();
  };
  this.startTimer = function () {
    this.opt.startTime = new Date().getTime();
  };
  this.endTimer = function () {
    return ((this.opt.startTime) ? new Date().getTime() - this.opt.startTime : null);
  };
});

我还发布了完整的 example on GitHub。项目是使用 generator-m 生成的。您可以使用 gulp watch 命令简单地克隆 github 和 运行 它。