Größere Datenmengen mit JavaScript performant durchsuchen

Seite 2: AngularJS, fuse.js

Inhaltsverzeichnis

Wie erwähnt bringt AngularJS mit den Filtern eine benutzerfreundliche Implementierung für die Suche mit, die häufig in Tabellen zum Einsatz kommt. Typischerweise wird dabei das Suchergebnis nach jedem eingegebenen Zeichen aktualisiert. Das sieht beispielsweise so aus:

Mit AngularJS lassen sich Suchen mit Filtern realisieren (Abb. 1).

Filter funktionieren vergleichsweise einfach und lassen sich direkt im HTML setzen:

<tr ng-repeat="entry in ctrl.dataset | filter:ctrl.term">

Diese Einfachheit gibt es jedoch nicht umsonst. Um Änderungen zwischen View und Datenmodell zu erkennen, nutzt AngularJS derzeit das sogenannte Dirty Checking. Vereinfacht gesagt wird dabei jede Zustandsvariable, die an ein HTML-Element geknüpft ist, bei allen Benutzeraktionen auf Änderungen geprüft. Je mehr Objekte von AngularJS überwacht werden, desto häufiger und letztlich langsamer ist das Dirty Checking. Eine Filterung würde somit auch bei Interaktionen ausgeführt, die die Suche überhaupt nicht betreffen.

Weiterhin werden Filter oft mit weiteren Operationen (z. B. Sortierung) verkettet, was die Performance weiter senkt:

<tr ng-repeat="entry in ctrl.dataset | filter:ctrl.term |
orderBy:entry.name">

Eine pragmatische Lösung ist das Auslagern der Filterlogik in eine eigene Service-Methode, die dann wiederum vom Controller nur bei Änderungen im Eingabefeld aufgerufen wird (über ngChange).

Im Beispielprojekt ist das entsprechend implementiert:

<input type="text" placeholder="Suchbegriff" ng-model="searchCtrl.term"
ng-change="searchCtrl.search(searchCtrl.term)">

Es gibt noch einige weitere Möglichkeiten zur Verbesserung der Performance von AngularJS-Filtern. Wenn beispielsweise ein Datensatz eine eindeutige Kennung (ID) hat, sollte man sie referenzieren. Andernfalls vergibt AngularJS automatisch eine ID, die in dem Fall unnötig wäre:

<tr ng-repeat="entry in ctrl.dataset track by entry.id">

Weitere Optimierungen sind im Folgenden nur stichpunktartig erwähnt und sollten einem erfahrenen AngularJS-Entwickler geläufig sein:

  • One-Time Bindung für alle Suchergebnisse verwenden (Two-Way Binding nur nötig, falls Datensätze zu bearbeiten sind)
  • generell möglichst wenige $watch-Handler setzen, da sie das Programm ebenfalls in jedem Dirty-Checking-Zyklus ausführen
  • Falls Elemente abhängig vom Zustand angezeigt werden, sollte man ngIf statt ngShow verwenden, da das entsprechende DOM-Element dann gar nicht erst gerendert wird.

Eine Ähnlichkeitssuche kann bereits bei einer unvollständigen Eingabe sinnvolle Vorschläge liefern und korrigiert Fehler praktisch automatisch. Fuse.js braucht wenig Speicher, ist vergleichsweise einfach zu konfigurieren und hat keine weiteren Abhängigkeiten. In den Optionen lassen sich die Groß- und Kleinschreibung sowie die Fehlertoleranz der Suche konfigurieren.

var fuse = new Fuse(data, fuseOptions);
var searchResult = fuse.search(term);

Für den Vergleich wurden die Standardeinstellungen verwendet. Die von fuse.js zurückgelieferten Ergebnisse waren sinnvoll und passten zu den Eingaben. Sie ist die einzige betrachtete Suche, die die Ergebnisse auch in einer sortierten Reihenfolge zurückliefert.