거대한 데이터 세트 (angular.js)에서 ngRepeat의 성능을 향상시키는 방법은 무엇입니까?
약 10MB의 필드, 약 2MB의 데이터가있는 수천 행의 거대한 데이터 세트가 있습니다. 브라우저에 표시해야합니다. 가장 간단한 접근 방식 (데이터 가져 오기, 넣기 $scope
, ng-repeat=""
작업 수행)은 제대로 작동하지만 DOM에 노드 삽입을 시작하면 약 0.5 분 동안 브라우저가 정지됩니다. 이 문제에 어떻게 접근해야합니까?
한 가지 옵션은 $scope
점진적으로 행을 추가 ngRepeat
하고 한 청크를 DOM에 삽입 하기 를 기다렸다가 다음 행으로 이동하는 것입니다. 그러나 AFAIK ngRepeat는 "반복"이 끝나면 다시보고하지 않으므로 추악합니다.
다른 옵션은 서버의 데이터를 페이지로 분할하여 여러 요청에서 가져 오는 것이지만 훨씬 더 나쁩니다.
과 같은 것을 찾기 위해 Angular 설명서를 살펴 보았지만 ng-repeat="data in dataset" ng-repeat-steps="500"
아무것도 찾지 못했습니다. 나는 Angular 방식에 상당히 익숙하지 않으므로 포인트가 완전히 누락되었을 수 있습니다. 이 모범 사례는 무엇입니까?
@ AndreM96에 동의하는 가장 좋은 방법은 제한된 양의 행, 더 빠르고 더 나은 UX 만 표시하는 것입니다. 이는 페이지 매김 또는 무한 스크롤로 수행 할 수 있습니다.
Angular를 사용한 무한 스크롤은 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 지시문 및 이와 유사한 다른 구현 방식으로 구현됩니다. 이에 대한 멋진 용어는 '오 클루 전 컬링 (Occlusion culling)' 이지만, 요약하면 다음과 같이 요약 할 수 있습니다. , 제한 사용자 만 많은 요소를 볼 수에 .
일반적으로 "무한 스크롤"로 알려진 것과 같은 작업을 수행하는 경우 초기 DOM 수를 약간 줄이려고하지만 몇 가지 새로 고침 후에는 모든 새 요소가 맨 아래에 고정되어 있기 때문에 빠르게 증가합니다. 스크롤은 요소 수에 관한 것이기 때문에 스크롤이 크롤링됩니다. 그것에 대해 무한한 것은 없습니다.
반면, collectionRepeat
접근 방식은 뷰포트에 맞는만큼의 요소 만 사용한 다음 재활용하는 것 입니다. 한 요소가 시야에서 벗어나면 렌더 트리에서 분리되어 목록의 새 항목에 대한 데이터로 다시 채워진 다음 목록의 다른 쪽 끝에있는 렌더 트리에 다시 연결됩니다. 이것은 사람이 DOM으로 들어오고 나가는 새로운 정보를 얻는 가장 빠른 방법으로, 기존의 생성 / 파괴주기 인 생성 / 파괴주기보다는 제한된 기존 요소 세트를 사용합니다. 이 방법을 사용하면 무한 스크롤을 실제로 구현할 수 있습니다 .
Ionic을 사용하여 / hack / adapt collectionRepeat
또는 이와 유사한 도구 를 사용할 필요는 없습니다 . 이것이 그들이 오픈 소스라고 부르는 이유입니다. :-) (이온 팀은주의를 기울일만한 아주 독창적 인 일을하고 있습니다.)
React에서 매우 비슷한 것을하는 훌륭한 예가 적어도 하나 있습니다. 업데이트 된 컨텐츠가있는 요소를 재활용하는 대신 트리에서 보이지 않는 것을 렌더링하지 않기로 선택하는 것입니다. 매우 간단한 POC 구현으로 약간의 깜박임이 허용되지만 5000 항목에서 빠르게 타 오르고 있습니다 ...
또한 ... 다른 게시물 중 일부를 반향하려면 track by
작은 데이터 세트에서도 사용하는 것이 매우 유용합니다. 필수라고 생각하십시오.
나는 이것을 볼 것을 권장합니다 :
AngularJS 최적화 : 1200ms ~ 35ms
그들은 네 부분으로 ng-repeat를 최적화함으로써 새로운 지시를 내 렸습니다.
최적화 # 1 : 캐시 DOM 요소
최적화 # 2 : 집계 감시자
최적화 # 3 : 요소 생성 지연
최적화 # 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>
즐겨!
트랙 바이 및 더 작은 루프와 같은 위의 모든 힌트 외에도이 힌트는 많은 도움이되었습니다.
<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/
모든 행의 높이가 동일한 경우 가상화 ng-repeat를 반드시 확인해야합니다. http://kamilkp.github.io/angular-vs-repeat/
이 데모 는 매우 유망 해 보이며 관성 스크롤을 지원합니다.
"추적"을 사용하여 성능을 향상시킬 수 있습니다.
<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 : 사용자가 아무것도 기다리지 않도록하십시오.
빈 화면이 나타나기 전에 3 초를 기다리는 것보다 10 초가 걸리는 수명이 늘어나는 페이지가 더 빨리 나타나고 한 번에 모두 얻을 수 있습니다.
따라서 페이지를 빠르게 만드는 대신 최종 결과가 느리더라도 페이지 를 빠르게 표시 하십시오 .
function applyItemlist(items){
var item = items.shift();
if(item){
$timeout(function(){
$scope.items.push(item);
applyItemlist(items);
}, 0); // <-- try a little gap of 10ms
}
}
위의 코드는 목록이 행 단위로 증가하는 것으로 보이며 항상 한 번에 렌더링하는 것보다 항상 느립니다. 그러나 사용자 에게는 더 빠릅니다.
가상 스크롤 은 거대한 목록과 큰 데이터 세트를 처리 할 때 스크롤 성능을 향상시키는 또 다른 방법입니다.
이를 구현하는 한 가지 방법 은이 데모에서 50,000 개의 항목으로 설명 된대로 Angular Material md-virtual-repeat
을 사용하는 것입니다.
가상 반복 문서에서 바로 가져옵니다.
가상 반복은 컨테이너를 채우고 사용자가 스크롤 할 때 컨테이너를 채우고 재활용 할 수있는 충분한 dom 노드 만 렌더링하는 ng-repeat의 제한된 대체입니다.
다른 버전 @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);
}
});
때때로 무슨 일이 있었는지, 당신은 몇 MS의 서버에서 데이터 (또는 백 엔드)를 취득 (예를 들어 내가이 100ms로 가정하고있어)하지만 그것은 우리의 웹 페이지에 표시 할 시간이 더 걸립니다 (의가가 900ms를 복용하고 있다고하자 디스플레이).
그래서 여기서 일어나는 일은 800ms입니다. 웹 페이지를 렌더링하는 데 걸리는 것입니다.
웹 응용 프로그램에서 수행 한 작업은 페이지 매김 을 사용 하여 데이터 목록을 표시 하는 것입니다 (또는 무한 스크롤을 사용할 수도 있음). 페이지 당 50 개의 데이터를 표시한다고 가정 해 보겠습니다.
따라서 모든 데이터를 한 번에로드 렌더링하지 않고 처음로드하는 50 개의 데이터 만 50ms 만 걸립니다 (여기서는 가정합니다).
사용자가 다음 페이지를 요청한 후 다음 50 개의 데이터를 표시하는 등 총 시간이 900ms에서 150ms로 줄었습니다.
이것이 성능 향상에 도움이되기를 바랍니다. 모두 제일 좋다
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)\"> </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;
}
}
큰 데이터 세트 및 다중 값 드롭 다운의 경우보다 사용하는 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>
'Programming' 카테고리의 다른 글
JOIN 또는 WHERE 내의 조건 (0) | 2020.05.29 |
---|---|
contenteditable element (div)에서 캐럿 (커서) 위치를 설정하는 방법은 무엇입니까? (0) | 2020.05.29 |
Windows 용 XAMPP에서 PHP를 업그레이드 하시겠습니까? (0) | 2020.05.29 |
기호 배열에 대한 문자 표기법이 있습니까? (0) | 2020.05.29 |
파일 연결 해제 실패 (0) | 2020.05.29 |