AngularJSの長いリストのパフォーマンスの最適化

AnglarJSは素晴らしいです! しかし、複雑なデータ構造を含む大きなリストを使用すると、作業が非常に遅くなります! 管理パネルをAngularJSに移植するときにこの問題が発生しました。 約500行を表示するとき、遅滞なく機能するはずです。 ただし、最初の表示には最大7秒かかりました。 ひどい

実装に2つのボトルネックが見つかりました。 1つはng-repeat



ディレクティブに関連付けられ、もう1つはフィルターに関連付けられました。

この記事では、発生したパフォーマンスの問題を解決または軽減するためのさまざまなアプローチを使用した実験の結果について説明します。 これにより、自分の強みをどこに置くことができ、どのアプローチを使用すべきでないかについてのアイデアとヒントが得られます。



ng-repeatディレクティブが大きなリストでゆっくり動作するのはなぜですか?


2500を超える要素を持つリストに対して双方向バインディングが行われると、 ng-repeat



ディレクティブの動作が遅くなります。 Misko Heveryの投稿でこれについてもっと読むことができます。 これは、AngularJSが「ダーティチェック」メソッドを使用して変更を追跡するためです。 各変更追跡には時間がかかるため、複雑なデータ構造を持つ大きなリストでは、アプリケーションの速度が低下します。



パフォーマンス分析に使用される前提条件



指令の労働時間の追跡:

リストの表示時間を測定するために、 $last



プロパティを使用してng-repeat



期間を測定する簡単なディレクティブを作成しました。 基準日はTimeTracker



サービスに保存されるため、結果はサーバーからのデータのダウンロードに依存しません。

 //       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  : " + (end - ref) + " ms"); }); } }; } ]);
      
      





HTMLでの使用:

 <tr ng-repeat="item in items" post-repeat-directive></tr>
      
      





Chromeの開発ツールを使用した履歴追跡の機能

Chrome開発者ツールの[年代順]タブ(タイムライン)で、イベント、1秒あたりのブラウザーフレーム数(フレーム)、メモリの割り当て(メモリ)を確認できます。 メモリツールは、メモリリークを検出し、アプリケーションに必要なメモリ量を判断するのに役立ちます。 フレームリフレッシュレートが1秒あたり30フレーム未満の場合、ページのちらつきが問題になります。 フレームツールは、ページのパフォーマンス情報を表示します。 さらに、CPUがjavascriptを消費する時間を表示します。



リストのサイズを制限する基本設定



この問題を軽減する最善の方法は、表示されるリストのサイズを制限することです。 これは、ページネーションまたは無限スクロールによって実行できます。



ページネーション


ページネーション方法は、AngularJS limitTo



フィルター(バージョン1.1.4以降)とlimitTo



フィルターの組み合わせに基づいています。 このアプローチは、表示されるリストのサイズを制限することにより、表示時間を短縮します。 これは、表示時間を短縮する最も効果的な方法です。

 //      $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); }; //   startFrom angular.module('app').filter('startFrom', function() { return function(input, start) { return input.slice(start); };
      
      





HTMLでの使用。

 <!--     --> <button ng-repeat="i in getNumberAsArray(numberOfPages()) track by $index" ng-click="setCurrentPage($index)">{{$index + 1}}</button <!--   --> <tr ng-repeat="item in displayedItemsList | startFrom: currentPage * pageSize | limitTo:pageSize" /tr>
      
      





paginationを使用したくない、または使用できないが、遅いフィルターの問題がまだ心配な場合は、手順5を見るのが面倒にならず、 ng-hide



を使用して不要なリストアイテムを非ng-hide



してください。



無限のスクロール


私たちのプロジェクトでは、無限スクロールのオプションを考慮しませんでした。 この機能をさらに詳しく知りたい場合は、AngularJSの無限のスクロールプロジェクトにアクセスしください。



最適化ガイドライン



1.データバインディングなしでリストを表示する


これは、パフォーマンスの問題を引き起こすデータバインディングであるため、最も明らかなソリューションです。 リストを一度表示したいだけで、データを更新したり変更したりする必要がない場合は、データバインディングを削除することは素晴らしいことです。 残念ながら、この場合、データの制御は失われ、私たちには不向きでした。 それが面白い人には、さらにbindonceプロジェクトを見てください。



2.組み込みのメソッド呼び出しを使用してデータを取得しないでください


フィルターされたコレクションを取得するメソッドを使用して、コントローラーでフィルターされたリストを直接取得しないでください。 ng-repeat



は、 $ダイジェストサイクルごとにすべての式を評価します。 これは非常に頻繁に行われます。 この例では、 filteredItems()



はフィルター処理されたコレクションを返します。 実行が遅い場合、アプリケーション全体の速度が急速に低下します。

 <li ng-repeat="item in filteredItems()"> <!--,      .--> <li ng-repeat="item in items"> <!--     -->
      
      





3. 2つのリストを使用します(1つはビューを表示し、もう1つはデータソースとして表示します)


この便利なテンプレートの意味は、データリストから表示リストを分離することです。 これにより、複数のフィルターを事前に適用し、ビューにコレクションキャッシュを適用できます。 次の例は、非常に単純化された実装を示しています。 変数applyFilter



はコレクションキャッシュを表し、 applyFilter



メソッドapplyFilter



照合を行います。

 /*  */ //   var items = [{name:"John", active:true }, {name:"Adam"}, {name:"Chris"}, {name:"Heather"}]; //    $scope.displayedItems = items; //   var filteredLists['active'] = $filter('filter)(items, {"active" : true}); //   $scope.applyFilter = function(type) { if (filteredLists.hasOwnProperty(type){ //      $scope.displayedItems = filteredLists[type]; } else { /*     */ } } //   $scope.resetFilter = function() { $scope.displayedItems = items; }
      
      





ビューで:

 <button ng-click="applyFilter('active')"> </button> <ul><li ng-repeat="item in displayedItems">{{item.name}}<li></ul>
      
      







4. ng-showの代わりにng-ifを使用してテンプレートを補完します


追加のディレクティブまたはテンプレートを使用してリスト項目の追加情報を表示する場合、それをクリックする場合は、 ng-if (バージョン1.1.5以降)を使用します。 ng-if



は表示を無効にします( ng-show



はありng-if



)。 この場合、追加の要素が追加され、バインディングは必要なときに正確に解決されます。

 <li ng-repeat="item in items"> <p> {{ item.title }} </p> <button ng-click="item.showDetails = !item.showDetails">Show details</buttons> <div ng-if="item.showDetails"> {{item.details}} </div> </li>
      
      





5. ng-mouseenter、ng-mouseleaveなどのAngularJSディレクティブを使用しないでください。


私たちの意見では、組み込みのAngularJS ng-mouseenter



と、画面がちらつきます。 ブラウザのフレームレートは、ほとんどの場合、1秒あたり30フレーム未満でした。 純粋なjQueryを使用してアニメーション効果とホバー効果を作成すると、この問題の解決に役立ちます。 後でDOMに追加された要素から通知を受信するには、jQuery.live()でマウスイベントのみをラップすることを忘れないでください。



6.フィルタリングのプロパティを設定します。 ng-showで除外されたアイテムを隠す


長いリストでは、各フィルターが元のリストの独自のサブセットを作成するため、フィルターの動作も遅くなります。 多くの場合、初期データが変更されない場合、フィルターを適用した結果は変わりません。 これを使用するには、データのリストを事前にフィルター処理し、必要なときにフィルター結果を適用して、処理時間を節約できます。

ng-repeat



ディレクティブを使用してフィルターを適用すると、各フィルターは元のコレクションのサブセットを返します。 また、AngularJSは、フィルターによって除外された要素をDOMから削除し、 $destroy



イベントを発生さ$destroy



ます。これにより、 $scope



からも削除され$scope



。 入力コレクションが変更されると、フィルターを通過した要素のサブセットも変更されるため、再び要素が再描画または破棄されます。

ほとんどの場合、この動作は正常ですが、ユーザーが頻繁にフィルタリングを使用する場合やリストが非常に大きい場合、要素の継続的なリンクと破棄はパフォーマンスに大きく影響します。 フィルタリングを高速化するには、 ng-show



およびng-hide



ディレクティブを使用できます。 コントローラーでフィルターを計算し、各要素のプロパティを追加します。 このプロパティの値でng-show



を使用します。 この結果、 ng-hide



ディレクティブは、元のコレクション、 $scope



、およびDOMのサブセットから要素を削除する代わりに、特定のクラスを追加するだけです。



7.フィルタリングプロンプトの設定:入力データの転送


節6で説明した方法に加えて、繰り返しフィルタリングの問題を解決する別の方法は、ユーザー入力を送信することです。 たとえば、ユーザーが検索文字列を入力した場合、ユーザーが入力を終了した後にフィルターをアクティブ化する必要があります。

このアプローチを使用する良い例は、次のサービスです。 ビューおよびコントローラーで次のように使用します。

 /* */ //         350 . $scope.$watch('queryInput', function(newValue, oldValue) { if (newValue === oldValue) { return; } $debounce(applyQuery, 350); }); var applyQuery = function() { $scope.filter.query = $scope.query; };
      
      





 /* */ <input ng-model="queryInput"/> <li ng-repeat= item in items | filter:filter.query>{{ item.title }} </li>
      
      





さらに読むために



  1. 巨大なアプリケーションのプロジェクト編成
  2. Misko HeveryのStackOverflowは、Angularのデータバインディングパフォーマンスに関する質問に対する回答です。
  3. ng-repeatのパフォーマンスを改善するさまざまな方法に関する短い記事
  4. オンデマンドでビッグデータをダウンロードする
  5. 良い範囲の記事
  6. 動的テンプレート用のAngularJSプロジェクト
  7. データバインディングなしの表示



All Articles