web-dev-qa-db-de.com

AngularJS 'ng-filter' ist auf einem Array von ~ 1000 Elementen sehr langsam

Ich habe einen einfachen <input>-Suchfilter für eine Liste von Elementnamen in AngularJS eingerichtet.

Meine Liste sieht so aus:

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
  }

Ich durchlaufe diese Liste in Angular und drucke die Ergebnisse in einem <ul> für jede Kategorie aus.

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

Zur Verdeutlichung sieht selectedData so aus:

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

Diese Liste funktioniert gut, obwohl filter selbst auf meinem recht schnellen Computer ziemlich langsam ist. Wenn Sie einen Buchstaben in die Eingabe eingeben, dauert es 1-2 Sekunden, bis die Liste aktualisiert wird.

Ich bin mir bewusst, dass dies wahrscheinlich ist, weil ich ungefähr 1000 Artikel auf einmal filtere, aber ich habe an anderer Stelle keine Diskussion darüber gesehen.

Gibt es eine Möglichkeit, eine bessere Leistung aus dem Filter herauszuholen? 

34
JVG

Das Hauptproblem bei der Filtermethode besteht darin, dass bei jeder Änderung der Dom manipuliert wird. Daher ist nicht der Filter langsam, sondern die Konsequenzen. Eine Alternative ist die Verwendung von etwas wie:

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

auf dem wiederholten Element.

Wenn Sie etwas Code von @OverZealous ausleihen, können Sie das Verhalten folgendermaßen vergleichen:


Update: Mit Angular v1.2 kam die track by-Syntax. Was auch bei solchen Problemen hilft. Vorausgesetzt, die Elemente haben einige eindeutige Attribute, kann man Folgendes verwenden:

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

Dabei muss item.id für alle Elemente eindeutig sein. Mit track by werden nur die dom-Elemente entfernt, die nicht mehr in der endgültigen Liste enthalten sind, andere werden merken sein. Ohne track by ist die gesamte Liste immer neu gezeichnet. Kurz gesagt: viel weniger Dom-Manipulation = schnelleres Neuzeichnen.

55
Yoshi

Eine weitere interessante Optimierung besteht darin, den Modellwechsel erst zu einem bestimmten Zeitpunkt auszulösen.

Fügen Sie dies Ihrem Sucheingabefeld hinzu: ng-model-options = "{debounce: 500}"

Dadurch wird der Filter ausgelöst, wenn der Benutzer während 500 ms aufhört zu tippen.

Ich habe die obige Geige aktualisiert:

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

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

Ich habe eine Geige erstellt, um den angezeigten Code (teilweise) zu simulieren.

Auf meinem Computer, der zwar schnell, aber nicht superschnell ist, läuft das akzeptabel. Es ist etwas langsam, aber es filtert eine übermäßig lange Liste, die eine bidirektionale Bindung mit den Kontrollkästchen aufweist. Jedes Mal, wenn Sie einen Buchstaben eingeben, muss die gesamte Liste gescannt und Elemente entfernt (oder hinzugefügt) werden.

Ich denke, Ihre beste Wette für dieses Problem besteht darin, eine Art einfache Paginierung hinzuzufügen, wie in dieser StackOverflow Antwort gezeigt.

Hier habe ich mein Beispiel geändert, um Paginierung aufzunehmen . Sie werden wahrscheinlich in eine bessere Lösung investieren wollen als nur Next/Previous, aber dies zeigt Ihnen, wie das Ergebnis extrem schnell sein kann, wenn Sie nicht alles auf einmal anzeigen. Es wird immer noch die gesamte Liste durchsucht, aber die gerenderte Liste ist viel eingeschränkter.

Die Ergänzungen:

Fügen Sie dem Bereich im Controller Paging-Informationen hinzu:

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

Erstellen Sie einen neuen Filter, um von einer bestimmten Seite zu beginnen:

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

Fügen Sie der Ansicht Filter hinzu, um die Liste einzuschränken:

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

Fügen Sie der Seite Paginierungsschaltflächen hinzu:

    <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>
5
OverZealous

Jedes Mal, wenn Sie die Eingabetaste drücken, müssen alle Watch-Ausdrücke ausgeführt werden, und Sie müssen Ihren Code betrachten, wenn Sie eine ganze Menge davon haben. Wenn Elementnamen unveränderlich sind, können Sie beispielsweise https://github.com/abourget/ verwenden. abourget-angle das erlaubt dir zu schreiben:

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

Bei jedem Tastendruck müssen Sie 1000 weniger Überwachungsausdrücke ausführen.

Zusätzlich könnten Sie eine Art Drosselung bei der Eingabe verwenden, so dass der Filter nach 500ms vom letzten Tastendruck ausgelöst wird.

4
Tadeusz Wójcik

Sie können auch die Elemente einschränken, die mit einem "limitTo" -Filter angezeigt werden. Dadurch können Sie immer noch eine große Anzahl von Elementen in Ihrem Modell filtern, die Sie herausfiltern. Dies ist jedoch nicht so langsam, da Sie nicht versuchen, ALLE Elemente im DOM anzuzeigen.

Hier ist ein modifizierter Jsbin basierend auf früheren Antworten, wobei jedoch der limitTo-Filter angewendet wurde:

http://jsbin.com/IhOcaKo/1

4
George Witte

Keine Lösungen funktionieren für mich :( 

Zum Schluss habe ichGEL&OUML;STdas Problem so einfach gelöst:

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

probiere es einfach aus und werde gelöst ... :)

0
X-Coder