AngularJS'ng-filter'在大约1000个元素的阵列上非常慢

时间:2013-07-31 11:34:13

标签: angularjs angularjs-ng-repeat angularjs-filter

我为<input>中的项目名列表设置了一个简单的AngularJS搜索过滤器。

我的列表如下所示:

var uniqueLists = {
    category1: ['item1', 'item2', 'item3' ... 'item180' ], // Real list contains ~180 items
    category2: ['itemA', 'itemB', 'itemC' ... 'itemZZZ' ], // Real list contains ~1080 items
    category3: ['otheritem1', 'otheritem2', 'otheritem3' ]  // Real list contains 6 items
  }

我在Angular中遍历此列表,并在每个类别的<ul>中打印出结果。

<div ng-repeat="(key,val) in uniqueLists">
    <form ng-model="uniqueLists[index][0]">
        <input ng-model="searchFilter" type="text" />
            <ul>
                <li ng-repeat="value in val | filter: searchFilter">
                    <label>
                         <input type="checkbox" ng-model="selectedData[key][value]" />
                        {{value}}
                    </label>
                </li>
            </ul>
    </form>
</div>

为清楚起见,selectedData如下所示:

var selectedData = {category1: [item1:true], category2: [], category3: []); // if 'item1's checkbox is checked.

这个列表运行得很好,虽然filter非常滞后,即使在我非常快的计算机上也是如此。在输入中键入一个字母需要1-2秒才能更新列表。

我知道这可能是因为我一次过滤掉大约1000件物品,但我没有在其他地方看到过这方面的讨论。

有没有办法从过滤器中获得更好的性能?

6 个答案:

答案 0 :(得分:55)

过滤方法的主要问题是每次更改都会操纵dom,因此过滤器不是缓慢而是后果。另一种方法是使用类似的东西:

ng-show="([item] | filter:searchFilter).length > 0"

重复元素。

从@OverZealous借出一些代码,您可以使用以下内容来比较行为:


更新:使用Angular v1.2时出现了track by语法。这也有助于解决这些问题。如果元素具有一些唯一属性,则可以使用:

ng-repeat="item in items | filter:searchFilter track by item.id"

item.id必须在所有项目中都是唯一的。使用track by时,只会移除那些不再位于最终列表中的dom元素,其他元素将被记住。而没有track by,每次都会重绘。简而言之:更少的dom操作=更快的重绘。

答案 1 :(得分:21)

另一个有趣的优化是在一定时间内“不触发”模型更改。

将此添加到您的搜索输入字段:ng-model-options =“{debounce:500}”

如果用户在500ms内停止输入,则会触发过滤器。

我更新了上面的小提琴:

http://jsfiddle.net/CXBN4/14/

<input ng-model="searchFilter" type="text" ng-model-options="{debounce: 500}" />

答案 2 :(得分:5)

I created a fiddle to simulate (part of) the code you are showing.

在我的计算机上,这是快速但不是超快,运行良好。它有点慢,但它过滤了一个过长的列表,它与复选框有双向绑定。每次键入字母时,都必须扫描整个列表并删除(或添加)项目。

我认为解决这个问题最好的办法是添加一些简单的分页,如this StackOverflow回答所示。

Here I've modified my example to include pagination。您可能希望投资一个比 Next / Previous 更好的解决方案,但是如果您没有一次显示所有内容,则会显示结果如何非常快。它仍然搜索整个列表,但渲染列表更加有限。

增加:

将分页信息添加到控制器中的范围:

$scope.currentPage = 0;
$scope.pageSize = 20;
$scope.numberOfPages = function () {
    return Math.ceil($scope.items.length / $scope.pageSize);
}

创建一个新的过滤器,从特定页面开始:

app.filter('startFrom', function () {
    return function (input, start, pageSize) {
        start = +start; //parse to int
        pageSize = +pageSize;
        while (start > input.length) {
            start -= pageSize;
        }
        if (start < 0) {
            start = 0;
        }
        return input.slice(start);
    };
});

向视图添加过滤器以限制列表:

<li ng-repeat="value in items | filter:searchFilter |
        startFrom:currentPage*pageSize:pageSize | limitTo:pageSize">

向页面添加分页按钮:

    <div>
        <button ng-disabled="currentPage == 0" ng-click="currentPage=currentPage-1">Previous</button> {{ currentPage+1 }}/{{ numberOfPages() }}
        <button ng-disabled="currentPage >= items.length/pageSize - 1" ng-click="currentPage=currentPage+1">Next</button>
    </div>

答案 3 :(得分:4)

每次按下输入键时,所有需要执行的表达式都需要执行,并查看代码中有很多。 如果项目名称是不可变的,您可以使用例如https://github.com/abourget/abourget-angular,它允许您编写:

<label>
     <input type="checkbox" ng-model="selectedData[key][value]" />
     <span set-text='value'></span>
</label>

每次按键时,你只需要执行1000个监视表达式。

此外,您可以对输入使用某种限制,以便在上次击键后500ms后触发过滤器。

答案 4 :(得分:4)

您还可以使用“limitTo”过滤器限制将显示的项目。这允许您在模型中仍然有大量要过滤掉的项目,但它不会那么慢,因为您没有尝试显示DOM中的所有项目。

这是基于早期答案的修改后的jsbin,但是应用了limitTo过滤器:

http://jsbin.com/IhOcaKo/1

答案 5 :(得分:0)

没有解决方案适合我:(

最后我求助这个问题就这么简单了:

<li ng-repeat="value in val | filter: searchFilter | limitTo:200">

试试看并解决......:)