AngularJS – Einmal durchzählen bitte!

27.07.2015 in Enterprise Java |Lesezeit: 8 Minuten


angularjs flackern

 

AngularJS ist derzeit eines der beliebtesten Frameworks zur Realisierung von Single Page Applikationen. Übersteigt der Umfang jedoch den eines einfachen Showcases werden über kurz oder lang Performanzprobleme spürbar.

 

Genau ein solches Performanzproblem ist in einem Kundenprojekt aufgetreten. In einer ersten Analyse ließ sich das Problemfeld auf das Data Binding von AngularJS eingrenzen. Via Profiler wurde offensichtlich, dass die Verarbeitungsdauer des Data Bindings eine nicht unerhebliche Zeit in Anspruch nahm, je mehr Daten an die View gebunden werden. Mit dem Resultat, dass der Browser nur noch verzögert reagierte.

 

Watches sind Bestandteil des Data Bindings

 

Das Data Binding in AngularJS nutzt Scopes um Daten zwischen der View und der restlichen Anwendung austauschen. Parallel dazu reagieren die Scopes auf Änderungen in der Anwendung. Für diesen Effekt sorgen sogenannte Watches. Dabei handelt es sich um ein Konstrukt, welches Änderungen innerhalb von Scopes erfasst und zugehörige Listener Funktionen ausführt. AngularJS organisiert Scopes in einer Baumstruktur unterhalb eines RootScope und ordnet die Watches dem jeweiligen Scope unter, in dem ihre Gültigkeit definiert wurde.

 

Das erfolgt durch

 

  • One-Way Bindings: {{ expression }}
  • Two Way Bindings: ng-model
  • indirekt über Direktiven insbesondere bei ng-repeat
  • programmatisch über $watch

 

In der Praxis erweist sich der großflächige Einsatz dieses Konstrukts schnell als Performance Killer. Die Folgen sind flackern bzw. ein schlechtes Ansprechverhalten im Browser. Letzteres war das Symptom, welches sich in unserer Anwendung gezeigt hatte.

 

Um diesem Umstand entgegen zu wirken, soll nachfolgend das Problem der Watches näher beleuchtet werden. Des Weiteren soll gezeigt werden, wie daraus Kennzahlen für Optimierungsmaßnahmen generiert werden können.

 

Warum sind viele Watches problematisch?

 

Das Problem lässt sich anhand der Arbeitsweise von AngularJS erkennen.

 

 

AngularJS erweitert den Browser Event Loop um einen eigenen Prozess, in dem auf Nutzerinteraktionen und auf Änderungen im Datenmodell reagiert werden kann. Hierfür wird der Digest Zyklus angestoßen, welcher iterativ die Nachverarbeitung auf Basis der Änderungen im Datenmodell übernimmt.

 

Die Nachverarbeitung beinhaltet den Durchlauf aller Scopes mit den zugeordneten Watches und die Ausführung der Listener Funktionen, sofern sich bei einem der überwachten Felder Änderungen ergeben. Da sich bereits verarbeitete Felder nachträglich erneut ändern können, erfolgt die Verarbeitung der Watches solange, bis alle Änderungen abgearbeitet wurden oder ein festgelegtes Limit von zehn Durchläufen erreicht wurde und der Prozess infolgedessen abgebrochen wird.

 

Eine hohe Anzahl an Watches mit teuren Listener Funktionen führt dazu, dass die Ausführung der Apply Phase viel Zeit in Anspruch nimmt und die DOM Render Phase erst mit einer deutlichen Verzögerung ausgeführt wird.

 

Problem erkannt, Gefahr gebannt?

 

Kann dieses Problem in der eigenen Anwendung identifiziert werden, so liegt das Ziel klar auf der Hand. Die Zahl der Watches muss zumindest auf ein akzeptables Maß minimiert werden. Zwei Metriken dienen dabei als Anhaltspunkt für den Erfolg von durchgeführten Optimierungsmaßnahmen. Während man die Ausführungszeit in der Apply Phase über einen Profiler erfassen kann, gibt es keine Möglichkeit für die quantitative Erfassung von Watches. Doch gerade diese sind in komplexeren Anwendungen nicht eindeutig identifizierbar.

 

Ein möglicher Lösungsansatz bietet die countWatches Funktion. Diese macht sich die Baumstruktur der Scopes zu Nutze, um in einer Tiefensuche mit Hilfe von $$childHead und $$nextSibling alle registrierten Watches zu zählen. Diese werden in einer Liste $$watchers an den jeweiligen Scopes gehalten.

 

Mit countWatches überflüssige Watches identifizieren

 

 

Um beispielsweise alle Watches ausgehend vom HTML <body> Element zu zählen, müssen die beiden Code-Snippets nur miteinander verknüpft werden.

 

 

 

Der oben gezeigte Code Schnipsel bietet eine erste Möglichkeit Watches quantitativ zu erfassen, so dass das Ergebnis von Optimierungsmaßnahmen bewertet und potentiellen Problemen entgegengewirkt werden kann.

 

AngularJS liefert akzeptable Performance bis 2000 Watches
(tweet this)

 

Das Team um AngularJS beziffert als obere Grenze 2000 Watches bis zu der eine Anwendung eine akzeptable Performance liefert. Alles darüber hinaus kann AngularJS nicht effizient verarbeiten. Diese Grenze haben wir relativ schnell überschritten und konnten u.a. mit der countWatches Funktion Einsparungsmaßnahmen auf ihren Erfolg hin überprüfen.

 

Überflüssige Watches auf einmaliges Binding reduzieren!

 

Für die Optimierung bietet sich bspw. das einmalige Binding von Feldern über {{ ::expression }}. Es führt dazu, dass ein referenzierter Wert genau nur einmal ausgewertet und anschließend nicht mehr in der Nachverarbeitung berücksichtigt wird. Diese Methode bietet sich für jeglichen statischen Content wie bspw. Labels an.

 

Eine weitere Möglichkeit eröffnet sich durch eine Überarbeitung von Codestellen, die die ng-repeat Direktive verwenden. Sie multipliziert die in ihrer Klammer definierten Watches. Neben einer Einsparung dieser Direktive, kann auch die Aggregation von Watches zielführend sein.

 

Mart Köhler

Mart Köhler

Mart Köhler ist Senior Enterprise Developer bei der open knowledge GmbH in Oldenburg.
Kontakt zum Autor:|