programing

대규모 데이터 세트(angular.js)에 비해 ngRepeat의 성능을 향상시키는 방법

mytipbox 2023. 2. 27. 23:24
반응형

대규모 데이터 세트(angular.js)에 비해 ngRepeat의 성능을 향상시키는 방법

수천 개의 행으로 이루어진 거대한 데이터 세트가 있으며, 각 행마다 약 10개의 필드와 약 2MB의 데이터가 있습니다.이치노 접근법 데이터 , )$scope, let, 렛츠ng-repeat=""정상적으로 동작합니다만, DOM 에 노드를 삽입하기 시작하면, 약 30분간 브라우저를 정지합니다.이문 어떻 떻떻 떻? ???

가지 것입니다.$scopengRepeat다음 청크를 DOM에 삽입한 후 다음 청크로 이동합니다.그러나 AFAIK ngRepeat는 "반복"이 종료되었을 때 보고하지 않기 때문에 추악해집니다.

다른 옵션은 서버의 데이터를 여러 페이지로 분할하여 여러 요청으로 가져오는 것인데, 이는 더 추합니다.

Angular 하여 Angular 문서를 했습니다.ng-repeat="data in dataset" ng-repeat-steps="500"이치노저는 Angular ways에 익숙하지 않기 때문에 요점을 전혀 이해하지 못할 수도 있습니다.이 경우의 베스트 프랙티스는 무엇입니까?

@AndreM96의 의견에 동의합니다.가장 좋은 방법은 한정된 수의 행만 표시하는 것입니다.UX는 페이지 번호부여 또는 무한 스크롤로 표시할 수 있습니다.

각도가 있는 무한 스크롤은 limitTo 필터로 매우 간단합니다.초기 제한을 설정하고 사용자가 추가 데이터를 요구할 때(간단함을 위해 버튼을 사용 중) 제한을 늘리면 됩니다.

<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이 있습니다.

대규모 데이터셋으로 이러한 과제를 해결하는 가장 핫하고 확장성이 높은 접근방식은 Ionic의 CollectionRepeat 지침 및 이와 같은 기타 구현 방식에 의해 구현됩니다.이것을 「폐색 도태라고 하는 고급 용어입니다만, 다음과 같이 요약할 수 있습니다.렌딩된 DOM 요소의 수를 50, 100, 500과 같은 임의의 페이지 번호(그러나 여전히 높음)로 제한하지 마십시오.대신 사용자가 볼 수 있는 요소 수만큼만 제한하십시오.

일반적으로 "무한 스크롤"이라고 알려진 작업을 수행하면 초기 DOM 카운트가 다소 줄어들지만 두 번 새로 고친 후에는 새로운 요소가 모두 맨 아래에 부착되기 때문에 빠르게 늘어납니다.스크롤은 요소 수가 중요하기 때문에 스크롤은 크롤이 됩니다.무한은 없어요.

한,는collectionRepeat접근법은 뷰포트에 맞는 만큼의 요소만 사용한 후 재활용하는 것입니다.한 요소가 화면 밖으로 회전하면 해당 요소는 렌더 트리에서 분리되고 목록의 새 항목에 대한 데이터로 채워진 다음 목록의 다른 끝에 있는 렌더 트리에 다시 연결됩니다.이는 기존의 생성/파괴 사이클이 아닌 제한된 기존 요소 세트를 사용하여 DOM 안팎에서 새로운 정보를 얻는 가장 빠른 방법입니다.작성/삭제합니다.이 방법을 사용하면 무한 스크롤을 구현할 수 있습니다.

을 사용할collectionRepeat이치:-) (이렇게 하다)


React에 아주 비슷한 일을 한 훌륭한 예가 적어도 한 가지 있습니다.업데이트된 컨텐츠로 요소를 재활용하는 대신 트리에서 표시되지 않는 항목을 렌더링하지 않을 뿐입니다.5000개의 아이템으로 빠르게 증가하고 있지만, 매우 간단한 POC 구현으로 약간의 깜박임이 발생합니다.


「」를 투고를 , 「」를 사용합니다. 른른른른른track by데이터셋이 작아도 큰 도움이 됩니다.의무라고 생각해.

다음을 참조할 것을 권장합니다.

각도 최적화JS: 1200 ~35 ms

그들은 ng-timeout을 4개의 파트에서 최적화함으로써 새로운 지시를 내렸다.

최적화 #1: DOM 요소 캐시

최적화 #2: 집약 감시자

최적화 #3: 요소 생성 지연

Optimization #4: 숨겨진 요소에 대한 감시자 바이패스

프로젝트는 여기 github에 있습니다.

사용방법:

1- 한 페이지 앱에 다음 파일을 포함합니다.

  • core.discloss.conflossible 。
  • scalyr.displaces
  • 교활하게 평가하다.js
  • slirepeat.js

2 - 모듈 의존성 추가:

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

3-ng-interface 교체

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

엔조이!

위의 모든 힌트들 외에 트랙 바이와 작은 루프들 또한 나에게 많은 도움을 주었다.

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

이 코드 조각은 로드된 후 이름을 인쇄하고 그 후에는 더 이상 보지 않습니다.마찬가지로 ng-repeat의 경우 다음과 같이 사용할 수 있습니다.

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

단, Angular에 대해서만 동작합니다.JS 버전 1.3 이후http://www.befundoo.com/blog/optimizing-ng-repeat-in-angularjs/ 에서

"추적 기준"을 사용하여 성능을 향상시킬 수 있습니다.

<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

모든 행의 높이가 동일한 경우 가상화 ng-module을 반드시 살펴보시기 바랍니다.http://kamilkp.github.io/angular-vs-repeat/

데모는 매우 유망해 보입니다(관성 스크롤 지원).

가상 스크롤은 방대한 목록과 대규모 데이터 세트를 처리할 때 스크롤 성능을 향상시키는 또 다른 방법입니다.

이를 구현하기 위한 한 가지 방법은 각도 재료를 사용하는 것입니다. md-virtual-repeat50,000개의 아이템으로 이 데모에서 시연되었듯이

가상 반복에 대한 문서에서 직접 인용하면:

가상 반복은 ng-repeat의 제한된 대체 수단이며 컨테이너를 채우고 사용자가 스크롤할 때 재사용할 수 있는 충분한 돔노드만 렌더링합니다.

규칙 1: 사용자는 어떤 것도 기다리지 않는다.

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
    }
}

위의 코드는 리스트가 행별로 증가하고 있는 것처럼 표시되며, 동시에 렌더링하는 것보다 항상 느립니다.그러나 사용자에게는 더 빠른 것으로 보입니다.

다른 버전 @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);
    }       
});

서버(또는 백엔드)로부터 데이터를 몇 밀리초만에 얻을 수 있는 경우가 있습니다만(예를 들어 100 밀리초라고 가정합니다), Web 페이지에 표시하는 데 시간이 걸립니다(표시하는 데 900 밀리초가 걸립니다).

여기서 일어나고 있는 것은 800ms입니다.웹 페이지를 렌더링하는 데만 시간이 걸립니다.

웹 어플리케이션에서 수행한 작업은 페이지 번호 부여(또는 무한 스크롤 사용 가능)를 사용하여 데이터 목록을 표시했습니다.예를 들어 페이지당 50개의 데이터를 표시한다고 칩시다.

그래서 한 번에 모든 데이터를 로드하는 것이 아니라 처음에 로드하는 50개의 데이터만 50밀리초(여기에서는 50밀리초라고 가정합니다).

따라서 총 시간이 900ms에서 150ms로 단축되고 사용자가 다음 페이지를 요청하면 다음 50개의 데이터가 표시됩니다.

이것이 성능 향상에 도움이 되기를 바랍니다.행운을 빌어요.

Created a directive (ng-repeat with lazy loading) 

페이지 하단에 도달하면 데이터를 로드하고 이전에 로드된 데이터의 절반을 제거하고 다시 div의 상단에 도달하면 이전 데이터가 로드됩니다(페이지 번호에 따라 다름). 따라서 DOM에는 제한된 데이터만 존재하므로 다음 사용자를 렌더링하는 대신 성능이 향상될 수 있습니다.데이터를 로드합니다.

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 그리드를 사용한 무한 스크롤 데모

값 개일 , 규모데데중중중중중중중중중중중 for for for for for for for for for for for for for for for for for for for for for for 를 사용하는 것이 .ng-optionsng-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>

언급URL : https://stackoverflow.com/questions/17348058/how-to-improve-performance-of-ngrepeat-over-a-huge-dataset-angular-js

반응형