Webanwendungen mit AngularJS

Seite 4: Datenbindung und Infrastruktur

Inhaltsverzeichnis

Zu guter Letzt bleibt die Frage, wie die Datenbindung in AngularJS funktioniert: Anders als beispielsweise Knockout benötigt AngularJS hierfür keine besonderen Objekte, sondern kann beliebige JavaScript-Objekte nutzen. Auf scheinbar magische Weise bemerkt AngularJS eine Aktualisierung und überträgt sie in das angebundene Steuerelement beziehungsweise Objekt.

Die zugrunde liegende Idee ist verhältnismäßig einfach: AngularJS kennt innerhalb eines jeden Controllers die Funktion $apply, die am jeweiligen Kontext zur Verfügung steht. Sie gleicht die Werte in den Steuerelementen mit denen des Kontexts ab. Damit die Datenbindung funktioniert, muss AngularJS also lediglich sicherstellen, dass jede Änderung eines Werts den Aufruf der Funktion bewirkt.

Eine Veränderung kann prinzipiell zwei Ursachen haben. Entweder wurde sie vom Anwender ausgelöst oder der Kontext wurde programmatisch aktualisiert. Da im ersten Fall in der Regel eine Direktive wie ng-click oder ng-submit involviert ist, kann AngularJS entsprechend reagieren.

Anders verhält es sich im zweiten Fall. Hier ändert sich der Zustand der Anwendung auf einem Weg, den AngularJS nicht überwachen kann. Folglich ruft es die benötigte Funktion nicht auf, die Datenbindung greift nicht und weder die Steuerelemente noch die zugehörigen Objekte werden aktualisiert. Das passiert häufiger, als man zunächst vermuten könnte: Ereignisse in der UI, die AngularJS nicht über eine Direktive behandelt, zählen ebenso zu den Verursachern wie sämtliche Ereignisse im JavaScript-Code. Insbesondere die Funktionen setTimeout und setInterval des Webbrowsers sowie die Kommunikation mit dem Webserver per AJAX oder Websockets sind dabei zu nennen.

Deshalb bewirkt das Zuweisen eines neuen Werts an die Variable $scope.foo innerhalb des setTimeout-Callbacks im folgenden Beispiel keine Aktualisierung der Anzeige:

app.controller('FooController', [ '$scope', function ($scope) {
$scope.foo = 23;
setTimeout(function () {
$scope.foo = 42;
}, 5 * 1000);
}]);

Die Lösung ist einfach: Man muss $apply von Hand aufrufen und den Callback darin kapseln. AngularJS stellt die Funktion am Kontext zur Verfügung:

app.controller('FooController', [ '$scope', function ($scope) {
$scope.foo = 23;
setTimeout(function () {
$scope.$apply(function () {
$scope.foo = 42;
});
}, 5 * 1000);
}]);

Für einige Szenarien verfügt AngularJS über vorgefertigte Dienste, die den Aufruf von $apply eigenständig durchführen. Hierzu zählt beispielsweise der $timeout-Service, der als Ersatz für die setTimeout-Funktion dient. Verwendet man ihn, lässt sich das Beispiel ohne $apply schreiben:

app.controller('FooController', [ '$scope', '$timeout',
function ($scope, $timeout) {
$scope.foo = 23;
$timeout(function () {
$scope.foo = 42;
}, 5 * 1000);
}
]);

Allerdings enthält AngularJS keinen $interval-Service, sodass man ihn im Bedarfsfall entweder selbst implementieren oder auf die klassische Variante zurückgreifen und die $apply-Funktion von Hand aufrufen muss.

Für die Kommunikation mit dem Webserver enthält AngularJS die beiden vorgefertigten Services $http und $resource, die zwar prinzipiell beide AJAX-Anfragen durchführen, sich aber in ihrem Abstraktionsgrad unterscheiden.

Um eine AJAX-Anfrage zu versenden, genügt im einfachsten Fall der Aufruf des $http-Services mit einem Parameterobjekt, das die Adresse und die zu verwendende Methode definiert. Er gibt ein Promise-Objekt zurück, an dem die beiden Funktionen success und error zur Verfügung stehen, die man verkettet angeben kann:

$http({ method: 'GET', url: '/articles' })
.success(function (data, status, headers, config) {
// ...
})
.error(function (data, status, headers, config) {
// ...
});

Da es auf Dauer unpraktisch und unnötig viel Schreibaufwand ist, die Methode stets innerhalb des Parameterobjekts zu übergeben, stellt der $http-Service entsprechende Komfortfunktionen zur Verfügung. Mit ihm lässt sich der gezeigte Aufruf alternativ folgendermaßen formulieren:

$http.get('/articles')
.success(function (data, status, headers, config) {
// ...
})
.error(function (data, status, headers, config) {
// ...
});

Außer get stellt AngularJS die Funktionen head, post, put, delete und jsonp zur Verfügung. Letztere ermöglicht das einfache Versenden von Cross-Domain-Anfragen und ist daher von Zeit zu Zeit ausgesprochen hilfreich.

Um bei einer Anfrage zusätzliche Daten an den Webserver zu übertragen, genügt es, ein entsprechendes Objekt als zweiten Parameter anzugeben:

var article = {
id: 23,
title: 'Node.js & Co.',
author: 'Golo Roden'
};
$http.post('/articles/23', article)
.success(...)
.error(...);

Der $resource-Service baut auf $http auf und kapselt Aufrufe an REST-Webdienste, sodass man nicht zwingend direkt mit dem HTTP-Protokoll hantieren muss. Alternativ stehen auch entsprechende Services von Drittanbietern zur Verfügung, wie Restangular.

Für die Anbindung von WebSockets enthält AngularJS serienmäßig keine Unterstützung, allerdings kann man einen entsprechenden Dienst mit wenigen Zeilen selbst schreiben.