January 22, 2015

AngularJS Performace durch One-Time-Binding verbessern

AngularJS 1.3 wurde im Oktober 2014 veröffentlicht und bringt neben zahlreichen Bugfixes auch neue Features. Als Feature bezeichnen die Angular JS Entwickler auch das fallenlassen der Unterstützung des Internet Explorer 8. Das Feature auf das sich dieser Artikel bezieht wurde mit der Version 1.3.0-beta.16 hinzugefügt.

Überblick über die Features in Angular 1.3

  • keine IE8 Unterstützung
  • ngMessages
  • $watchGroup
  • strict-DI
  • One-Time-Binding

Dieser Artikel fokussiert sich auf den letzen Punkt, das One-Time-Binding, das oft auch als BindOnce bezeichnet wird.

Das Problem

AngularJS bietet ein großartiges Data-Binding System, das es Entwicklern auf einfachste Weise ermöglicht Daten aus Controllern mit der UI zu verknüpfen. Das funktioniert mit kleinen Datenmengen einwandfrei und ohne Kompromisse. Müssen jedoch größere Datenmengen mit der UI verknüpft werden kann es zu Performance Problemen kommen, siehe hierzu das Beispiel 1. Es zeigt eine Tabelle mit ~2.500 Einträge mit Informationen über Städte (Städtenamen, Postleitzahlen und weiteren Informationen). Über der Tabelle ist ein Textfeld zu sehen. Wird das Textfeld fokussiert und eine beliebige Taste dauerhaft gedrückt ist zu erkennen, dass die UI einfriert und eine deutlich wahrnehmbare Verzögerung entsteht.

Die UI friert bei zu vielen Data-Bindings ein oder es kommt zu einer verzögerten Darstellung

Beispiel 1: Performance Issue in AngularJS 1.3

Die Ursache

In dem Beispiel 1 werden die Daten mit doppelten geschweiften Klammern an die UI gebunden, ebenso könnte auch die ngBind-Direktive genutzt werden. Zu Demonstrationszwecken werden sowohl Funktionen, wie auch Filter eingesetzt.

 

 

 

 

 

AngularJS erzeugt intern für jedes Binding einen Watcher, der die Property auf Änderungen überwacht und die Darstellung bei einer etwaigen Änderung aktualisiert. Hierzu bedient sich Angular einer Technik, die als Dirty-Checking bezeichnet wird. Der Dirty-Check ist simple Funktion und hat die Aufgabe Änderungen an einer Property festzustellen. Er wird in der Digest-Phase für jeden einzelnen Watcher durchgeführt und es wird der Callback eines Watchers getriggert wenn eine Änderung an einer Property festgestellt wurde. Es gilt der Grundsatz, dass eine Angular Anwendung ab ~2.000 Watchern¹ inperformant wird, da jeder Watcher in jedem Angular-internen $digest-Zyklus überprüft werden muss. Wie in Beispiel 1 zu sehen ist, hat Angular ~18.000 Watcher erzeugt, die dafür verantwortlich sind, dass die UI einfriert.

Die Lösung

Die Lösung des zuvor beschriebene Problems ist ebenso simpel wie effektiv. Das Angular Team hat sich auf eine nette Syntax geeinigt. So kann eine Angular Expression mit einem Doppelten Doppelpunkt geprefixt werden um den Watcher nach einmaliger Evaluierung zu entfernen, siehe hierzu Beispiel 2. Wenn nun das Textfeld fokussiert und eine beliebige Taste dauerhaft gedrückt wird, ist kein einfrieren der UI zu erkennen.

Wenn sich die Daten, die in der UI angezeigt werden nach dem initialen Laden nicht mehr ändern: One-Time-Binding verwenden

Beispiel 2: AngularJS 1.3 mit BindOnce

Neue Syntax

Angular Binding mit Watcher: {{zip.city | titleCase}}
Angular Binding ohne Watcher: {{::zip.city | titleCase}}
Wie zu sehen ist genügt es den Code dahingehend anzupassen, dass :: vor die Expression zu schreiben ist. Angular evaluiert die Expression einmalig und entfernt anschließend den Watcher. Zu beachten ist jedoch, dass eine Änderung des Datenmodells keine Aktualisierung der UI nach sich zieht, da die Properties nun nicht mehr überwacht werden.

Alternative

Da dieses Feature erst ab der Angular Version 1.3 verfügbar ist, muss für eine Angular Anwendung die eine Version vor 1.3 verwendet auf ein Modul aus der Community zurückgreifen. Das bindonce-Modul von Pasquale Vazzana ist auf GitHub https://github.com/Pasvaz/bindonce inklusive einer umfassenden Dokumentation zu finden. Beispiel 3 zeigt abschließend die Verwendung des BindOnce-Moduls mit der Angular Version 1.2

Für ältere Angular Versionen bindonce Direktive nutzen.

Beispiel 3: AngularJS 1.2 mit externem BindOnce-Modul²

Further Reading

Related Posts

theCodeCampus Autor Kai Henzler

Kai Henzler
Developer at thecodecampus </>

I'm a web developer who is around since the AngularJS days (10+ years). My focus is on teaching others how to write simple and maintainable code.


Leave a Reply

Add code to your comment in Markdown syntax.
Like this:
`inline example`

```
code block
example
```

Your email address will not be published.