AngularJS ng-repeat 很慢

时间:2021-01-24 14:38:40

标签: javascript angularjs

渲染许多条目并不是很慢。问题是每当 onAuthUIStateChange((authState, authData) => { console.log('LoginPage', authState, authData); if (this.authState == AuthState.SignedIn) { this.router.navigate(['']); } }); 更新时,它首先在元素的末尾添加新项目,然后在它匹配新的 $scope.data 时减少它。

例如:

$scope.data

此脚本正在更新 <div class="list" ng-repeat="entry in data"> <h3>{{entry.title}}</h3> </div>

$scope.data

假设我在 $scope.load = function() { $scope.data = getDataFromDB(); } 中有 5 个条目。条目是:

$scope.data

[ { id: 1, title: 1 }, { id: 2, title: 2 }, ...... ] 已经有这些条目然后被重新加载($scope.data 被调用)时,DOM 元素大约 0.1s - 0.2s 有 10 个元素(重复元素),然后在 0.1s 之后 - 0.2s 减少到 5.

所以问题是在更新 ng-repeat DOM 时有大约 0.1s - 0.2s 的延迟。当我实施实时搜索时,这看起来非常糟糕。每当它从数据库中更新时,ng-repeat DOM 元素都会在短时间内添加一次。

如何使渲染即时?


已编辑

我会把我所有的代码粘贴在这里:

控制器:

$scope.data = getDataFromDB();

视图:

$scope.search = function (table) {
    $scope.currentPage = 1;
    $scope.endOfPage = false;
    $scope.viewModels = [];
    $scope.loadViewModels($scope.orderBy, table);
}

$scope.loadViewModels = function (orderBy, table, cb) {
    if (!$scope.endOfPage) {
        let searchKey = $scope.page.searchString;
        let skip = ($scope.currentPage - 1) * $scope.itemsPerPage;
        let searchClause = '';

        if (searchKey && searchKey.length > 0) {
            let searchArr = [];
            $($scope.vmKeys).each((i, key) => {
                searchArr.push(key + ` LIKE '%` + searchKey + `%'`);
            });
            searchClause = `WHERE ` + searchArr.join(' OR ');
        }

        let sc = `SELECT * FROM ` + table + ` ` + searchClause + ` ` + orderBy +
            ` LIMIT ` + skip + `, ` + $scope.itemsPerPage;
        sqlite.query(sc, rows => {
            $scope.$apply(function () {
                var data = [];
                let loadedCount = 0;
                if (rows != null) {
                    $scope.currentPage += 1;
                    loadedCount = rows.length;
                    if (rows.length < $scope.itemsPerPage)
                        $scope.endOfPage = true

                    for (var i = 0; i < rows.length; i++) {
                        let item = rows.item(i);
                        let returnObject = {};
                        $($scope.vmKeys).each((i, key) => {
                            returnObject[key] = item[key];
                        });
                        data.push(returnObject);
                    }
                    $scope.viewModels = $scope.viewModels.concat(data);
                } 
                else
                    $scope.endOfPage = true;

                if (cb)
                    cb(loadedCount);
            })
        });
    }
}

调用它:

<div id="pageContent" class="root-page" ng-controller="noteController" ng-cloak>
    <div class="row note-list" ng-if="showList">
        <h3>Notes</h3>
        <input ng-model="page.searchString" id="search"
               ng-keyup="search('notes')" type="text" class="form-control"
               placeholder="Search Notes" style="margin-bottom:10px">
        <div class="col-12 note-list-item"
             ng-repeat="data in viewModels track by data.id"
             ng-click="edit(data.id)"
             ontouchstart="touchStart()" ontouchend="touchEnd()"
             ontouchmove="touchMove()">
            <p ng-class="deleteMode ? 'note-list-title w-80' : 'note-list-title'"
               ng-bind-html="data.title"></p>
            <p ng-class="deleteMode ? 'note-list-date w-80' : 'note-list-date'">{{data.dateCreated | displayDate}}</p>
            <div ng-if="deleteMode" class="note-list-delete ease-in" ng-click="delete($event, data.id)">
                <span class="btn fa fa-trash"></span>
            </div>
        </div>
        <div ng-if="!deleteMode" ng-click="new()" class="add-btn btn btn-primary ease-in">
            <span class="fa fa-plus"></span>
        </div>
    </div>
    <div ng-if="!showList" class="ease-in">
        <div>
            <div ng-click="back()" class="btn btn-primary"><span class="fa fa-arrow-left"></span></div>
            <div ng-disabled="!isDataChanged" ng-click="save()" class="btn btn-primary" style="float:right">
                <span class="fa fa-check"></span>
            </div>
        </div>
        <div contenteditable="true" class="note-title"
             ng-bind-html="selected.title" id="title">
        </div>
        <div contenteditable="true" class="note-container" ng-bind-html="selected.note" id="note"></div>
    </div>
</div>

<script src="../js/pages/note.js"></script>

sqlite 查询:

$scope.loadViewModels($scope.orderBy, 'notes');

它是 apache cordova 框架,所以它在 Android 模拟器中使用 webview。


我的代码结构

query: function (query, cb) {
    db.transaction(function (tx) {
        tx.executeSql(query, [], function (tx, res) {
            return cb(res.rows, null);
        });
    }, function (error) {
        return cb(null, error.message);
    }, function () {
        //console.log('query ok');
    });
},

所以控制器里面有控制器。父项为 <html ng-app="app" ng-controller="pageController"> <head>....</head> <body> .... <div id="pageContent" class="root-page" ng-controller="noteController" ng-cloak> .... </div> </body> </html> ,子项为 pageController。这样的结构是否会减慢 noteController 指令的速度?

顺便说一句,使用 ng-repeat 没有帮助。渲染时仍然有延迟。我也可以修改条目,所以当条目更新时,它也应该在列表中更新。


注意

经过彻底调查后,发现了一些奇怪的东西。通常 ng-repeat 项目中有 track by 键。在我的情况下 ng-repeat 项目没有它。这是问题的原因吗?

3 个答案:

答案 0 :(得分:2)

提高性能的一种方法是在 track by 表达式中使用 ng-repeat 子句:

<div class="list" ng-repeat="entry in data track by entry.id">
    <h3>{{entry.title}}</h3>
</div> 

来自文档:

<块引用>

最佳实践:如果您正在处理具有唯一标识符属性的对象,则应通过此标识符而不是对象实例进行跟踪,例如item in items track by item.id。如果您稍后重新加载数据,ngRepeat 将不必为它已经呈现的项目重建 DOM 元素,即使集合中的 JavaScript 对象已替换为新的对象。对于大型集合,这显着提高了渲染性能。

有关详细信息,请参阅

答案 1 :(得分:0)

在你的 html 中,试试这个:

<div class="list" ng-repeat="entry in data">
    <h3 ng-bind="entry.title"></h3>
</div> 

答案 2 :(得分:0)

经过深入研究,我发现了我的问题。每次我重置/重新加载我的 $scope.viewModels 时,我总是先将它分配给 null / empty array。这就是导致渲染延迟的原因。

示例:

$scope.search = function (table) {
    $scope.currentPage = 1;
    $scope.endOfPage = false;
    $scope.viewModels = []; <------ THIS
    $scope.loadViewModels($scope.orderBy, table);
}

因此,我没有将其分配给空/空数组,而是将其替换为新加载的数据,并且闪烁消失了。