Подтвердить что ты не робот

AngularJS 'ng-filter' очень медленно на массиве ~ 1000 элементов

У меня есть простой поисковый фильтр <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 checkbox is checked.

Этот список работает отлично, хотя filter довольно отсталый, даже на моем довольно быстром компьютере. При вводе буквы на вход требуется 1-2 секунды для обновления списка.

Я знаю, что это, вероятно, потому, что я фильтрую около 1000 элементов за раз, но я не видел никаких обсуждений этого в другом месте.

Есть ли способ улучшить производительность фильтра?

4b9b3361

Ответ 1

Основная проблема с подходом к фильтру заключается в том, что при каждом изменении управляется 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 = более быстрое перерисовка.

Ответ 2

Еще одна интересная оптимизация - "не запускать" изменение модели до определенного времени.

Добавьте это в поле ввода для поиска: ng-model-options = "{debounce: 500}"

Это вызовет фильтр, если пользователь перестанет печатать в течение 500 мс.

Я обновил приведенную выше скрипту:

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

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

Ответ 3

Я создал скрипт для моделирования (части) кода, который вы показываете.

На моем компьютере, который работает быстро, но не очень быстро, он работает хорошо. Он немного медленный, но он фильтрует слишком длинный список, который имеет двустороннюю привязку с флажками. Каждый раз, когда вы вводите письмо, весь список должен быть отсканирован, а элементы удалены (или добавлены).

Я думаю, что ваш лучший выбор для решения этой проблемы - добавить какую-то простую разбивку на страницы, как показано в fooobar.com/questions/47696/....

Здесь я изменил свой пример, чтобы включить разбивку на страницы. Вероятно, вы захотите инвестировать в лучшее решение, чем просто 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>

Ответ 4

Каждый раз, когда вы нажимаете клавишу во вводе, все часы должны быть выполнены и смотреть на ваш код, у вас их много. Если имена элементов неизменяемы, вы можете использовать, например, https://github.com/abourget/abourget-angular, который позволяет вам написать:

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

И у вас меньше 1000 выражений часов для каждого нажатия клавиши.

Кроме того, вы можете использовать какой-то дросселирование на входе, поэтому триггеры фильтра после 500 мс от последнего нажатия клавиши.

Ответ 5

Вы также можете ограничить элементы, которые будут отображаться с использованием фильтра "limitTo". Это позволяет вам по-прежнему иметь огромное количество элементов в вашей модели, которые вы отфильтровываете, но это будет не так медленно, потому что вы не пытаетесь показать ВСЕ элементы в DOM.

Вот модифицированный jsbin, основанный на более ранних ответах, но с примененным фильтром limitTo:

http://jsbin.com/IhOcaKo/1

Ответ 6

Никакие решения не работают для меня:(

Наконец, я SOLVED проблема эта просто:

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

просто попробуйте и решитесь...:)