Es dürfte mittlerweile bekannt sein, dass AngularJS Performance-Probleme hat, vor allem bei langen Listen mit Datenobjekten, und wenn man dies in Kombination mit ng-repeat nutzt. Der Grund hierfür ist einfach. Alles, was wir in Kombination mit der ng-bind Direktiven oder mit {{}} nutzen, wird von Angular einer sogenannten $watch-Liste hinzugefügt. Bei jedem $apply()-Aufruf geht Angular diese Liste durch und überprüft, ob sich etwas in dem Modell geändert hat, und wenn ja, dann wird die Änderung an die View weitergegeben. Dabei spielt es keine Rolle, ob wir $apply() aufrufen oder Angular intern den Aufruf macht. Es ist verständlich, dass, je länger die $watch-Liste ist, desto länger Modelländerungen brauchen, bis sie an die View weitergeleitet werden, und das macht unsere Anwendung träge.

Beispiel: Ein Lieferservice mit verschiedenen Gerichten

Stellen wir uns vor, wir haben eine Webseite für unseren Lieferservice, bei der Kunden online bestellen können. Die Webseite hat eine Liste von Gerichten, und jedes Gericht hat einen Button, den der Kunde nutzen kann, um das Gericht in seine Bestellung aufzunehmen. So könnte der Controller und das HTML für unsere Seite aussehen.

app.js

angular.module('dishes', [])
  .controller('MainCtrl', function($scope) {
    $scope.dishes = []; // Liste mit all unseren Gerichten

    $scope.orderDish = function(dish) {
      dish.inOrder = true;
      // Logic für die Bestellung
    }
  });

Ausschnitt aus index.html

<ul>
  <li ng-repeat="dish in dishes">
    <span>Name: {{dish.name}}</span>
    <div>{{dish.inOrder}}
      <button ng-click="orderDish(dish)">Gericht bestellen</button>
    </div>
  </li>
</ul>

Für jedes Gericht haben wir zwei $watches ({{dish.name}} und {{dish.inOrder}}) und noch ein $watch für das ng-repeat. Wenn wir also 30 Gerichte hätten, hätten wir auch 61 $watches. Das für sich alleine ist noch nicht problematisch, aber, wie schon erwähnt, wird Angular alle $watch es auf Änderungen überprüfen, sobald $apply() aufgerufen wird. Wenn wir auf den Button klicken, um ein Gericht zu bestellen wird $apply() intern, aufgerufen und Angular wird für alle Modelldaten überprüfen, ob es Änderungen gegeben hat. Was ist aber, wenn wir wissen, dass es für bestimmte Daten keine Änderungen geben kann, wie in unserem Beispiel für den Namen des Gerichts? Hier kommen One-time bindings ins Spiel. Mit deren Hilfe können wir Angular sagen: "Schreib Daten in die View beim Laden, aber danach interessieren uns Änderungen für bestimmte Daten nicht mehr." Indem wir One-time bindings nutzen, können wir Angular anweisen, für bestimmte Daten keine $watches erzeugen, und das macht unsere $watch-Liste kleiner und unsere Anwendung schneller.

Beispiel mit One-time bindings

Hier haben wir das gleiche Beispiel wie oben, aber diesmal mit One-time binding für den Namen des Gerichts. Unser app.js braucht keine Änderungen, nur die index.html-Datei bedarf minimaler Änderungen.

<ul>
  <li ng-repeat="dish in dishes">
    <span>Name: {{::dish.name}}</span>
    <div>{{dish.inOrder}}
      <button ng-click="orderDish(dish)">Gericht bestellen</button>
    </div>
  </li>
</ul>

Durch das Hinzufügen von :: vor dem dish.name haben wir Angular gesagt, dass es nicht überprüfen soll, ob der Namen des Gerichts sich geändert hat, und somit haben wir die Anzahl der $watches auf 31 reduziert. Überall dort, wo wir mit Daten arbeiten, die sich nach dem initialen Laden nicht mehr ändern, können wir One-time bindings nutzen. In unserem kleinem Beispiel wird die reduzierte Anzahl an $watches kaum einen Unterschied machen, aber je mehr Gerichte wir haben, desto größer ist der Unterschied, d. h. desto langsamer ist unsere Anwendung.

Fragen oder Anregungen?

Twitter: @jsperts_de

Github: JSperts

Xing: JSperts

E-Mail: info@jsperts.de

Newsletter

Möchten Sie Zugang zu diesen Informationen haben?

  • Gutscheine und Angebote für Workshops
  • Informationen zu Workshops (neue Termine, neue Inhalte)
  • Benachrichtigungen über neue Blogartikel zu Themen wie Angular, ES6, React, usw.