web-dev-qa-db-fra.com

AngularJS 'ng-filter' est très lent sur un tableau de ~ 1000 éléments

J'ai un filtre de recherche <input> simple configuré pour une liste de noms d'éléments dans AngularJS.

Ma liste ressemble à ceci:

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
  }

Je parcoure cette liste dans Angular et imprime les résultats dans un <ul> pour chaque catégorie.

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

Pour plus de clarté, selectedData ressemble à ceci:

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

Cette liste fonctionne très bien, bien que la filter soit assez lente, même sur mon ordinateur assez rapide. La saisie d’une lettre dans l’entrée nécessite 1 à 2 secondes pour que la liste soit mise à jour.

Je suis conscient du fait que c'est probablement parce que je filtre environ 1000 éléments à la fois, mais je n'ai vu aucune discussion à ce sujet ailleurs.

Existe-t-il un moyen d'améliorer les performances du filtre? 

34
JVG

Le problème principal de l’approche du filtre est qu’à chaque changement, le dom est manipulé, donc ce n’est pas le filtre qui est lent, mais les conséquences. Une alternative consiste à utiliser quelque chose comme:

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

sur l'élément répété.

En prêtant du code de @OverZealous, vous pouvez utiliser ce qui suit pour comparer le comportement:


Update: avec Angular v1.2, la syntaxe track by est apparue. Ce qui aide aussi avec de tels problèmes. À condition que les éléments aient un attribut unique, on peut utiliser:

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

item.id doit être unique pour tous les éléments. Avec track by, seuls seront supprimés les éléments dom qui ne figurent plus dans la liste finale, les autres seront mémorisés. Alors que sans track by, la liste complète est redessinée à chaque fois. En bref: beaucoup moins de manipulation de dom = redessin plus rapide.

55
Yoshi

Une autre optimisation intéressante consiste à «ne pas déclencher» le changement de modèle avant un certain temps.

En ajoutant ceci au champ de saisie de votre recherche: ng-model-options = "{debounce: 500}"

Cela déclenchera le filtre si l'utilisateur cesse de taper pendant 500 ms.

J'ai mis à jour le violon ci-dessus:

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

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

J'ai créé un violon pour simuler (une partie de) le code que vous montrez.

Sur mon ordinateur, qui est rapide mais pas super rapide, cela fonctionne bien. C'est un peu lent, mais il filtre une liste trop longue qui comporte une liaison bidirectionnelle avec les cases à cocher. Chaque fois que vous tapez une lettre, toute la liste doit être numérisée et les éléments supprimés (ou ajoutés).

Je pense que votre meilleur pari pour résoudre cela est d’ajouter une sorte de pagination simple, comme indiqué dans this StackOverflow answer.

Ici, j'ai modifié mon exemple pour inclure la pagination . Vous voudrez probablement investir dans une solution meilleure que juste Next/Previous, mais cela montre à quel point le résultat peut être extrêmement rapide si vous ne montrez pas tout en une fois. Il continue à parcourir toute la liste, mais la liste rendue est beaucoup plus limitée.

Les ajouts:

Ajoutez des informations de pagination à la portée du contrôleur:

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

Créez un nouveau filtre pour démarrer à partir d'une page spécifique:

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

Ajoutez des filtres à la vue pour limiter la liste:

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

Ajoutez des boutons de pagination à la page:

    <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

Chaque fois que vous appuyez sur la touche de saisie, toutes les expressions surveillées doivent être exécutées et le code vous en contient beaucoup . Si les noms des éléments sont immuables, vous pouvez utiliser par exemple https://github.com/abourget/ abourget-angular qui vous permet d’écrire:

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

Et vous avez 1000 expressions de veille de moins à exécuter sur chaque frappe.

De plus, vous pouvez utiliser une sorte de limitation à l'entrée pour que le filtre se déclenche après 500 ms à partir de la dernière frappe.

4
Tadeusz Wójcik

Vous pouvez également limiter le nombre d'éléments à afficher à l'aide d'un filtre "limitTo". Cela vous permet de conserver un grand nombre d'éléments dans votre modèle que vous filtrez, mais cela ne sera pas aussi lent car vous n'essayez pas d'afficher TOUS les éléments dans le DOM.

Voici un jsbin modifié basé sur des réponses précédentes, mais avec le filtre limitTo appliqué:

http://jsbin.com/IhOcaKo/1

4
George Witte

Aucune solution ne fonctionne pour moi :( 

Finalement, jeAI R&EACUTE;SOLUle problème de cette façon simplement:

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

essayez-le et soyez résolu ... :)

0
X-Coder