Single Page Applications mit AngularJS, Teil 1: Erste Schritte

ViewModel und Datenbindung

Da AngularJS über die Namen der verwendeten Parameter auf die zu injizierenden Services schließt, sind Probleme beim Einsatz von Minification programmiert. Der Grund dafür ist, dass im Zuge der Code-Komprimierung für gewöhnlich die Namen von Variablen und Parametern durch kürzere Bezeichner ersetzt werden. Dies hat zur Folge, dass die für AngularJS benötigten Informationen verloren gehen. Um sie zu erhalten, kann der Entwickler sie in Form von Strings im Quellcode deponieren. Das folgende Code-Beispiel veranschaulicht dies.

app.controller("FlugBuchenCtrl", 
["$scope", "$http", "$q", function ($scope, $http, $q)
{
$scope.vm = new FlugBuchenVM($scope, $http, $q);
}]);

Statt einer Funktion übergibt es an den zweiten Parameter von controller ein Array, das aus den Namen der zu injizierenden Services besteht und an der letzten Stelle die vom Controller zu verwendende Funktion enthält. Der erste Eintrag des Arrays umfasst den Namen des Dienstes, der in den ersten Parameter der Funktion zu injizieren ist, der zweite Eintrag den Namen des Services, der in den zweiten Parameter der Funktion zu injizieren ist, und so weiter.

Deklaration des ViewModel

Das folgende ViewModel repräsentiert einen Flug:

function FlugVM(flug) {
this.Id = flug.Id;
this.Abflugort = flug.Abflugort;
this.Zielort = flug.Zielort;

if (typeof flug.Datum == "string") {
this.Datum = moment(flug.Datum).toDate();
}
else {
this.Datum = flug.Datum;
}
}

Ein JavaScript-Objekt, das seine Funktion von einem HTTP-Service erhalten hat, initialisiert den Flug. Falls die Eigenschaft Datum nicht bereits als Date vorliegt, wird sie mit der Funktion moment aus der freien Bibliothek moment.js in ein Date-Objekt umgewandelt.

Das ViewModel in Listing 1 repräsentiert die gesamte Anwendung. Es nimmt den aktuellen Scope sowie den HTTP-Service und den Q-Service von AngularJS entgegen und hinterlegt ihn für die spätere Verwendung in entsprechenden Eigenschaften. Daneben bietet es eine Eigenschaft fluege an, die auf ein Array mit den abgerufenen Flügen verweist. Die Eigenschaft selectedFlug verweist auf jenen Flug, den der Benutzer ausgewählt hat, und message enthält Informationen wie Fehler- oder Statusmeldungen, die dem Benutzer anzuzeigen sind. Die Funktion loadFluege simuliert das Laden von Flügen, indem es zwei hartcodierte Flüge zum Array fluege hinzufügt. Alternativ lässt sich das ViewModel so implementieren, dass sich die Funktion loadFluege unter Verwendung der Eigenschaften flugNummerFilter, flugVonFilter und flugNachFilter zum Abrufen von Flügen an einen HTTP-Service wendet.

Die Funktion selectFlug nimmt den Index eines Flugs des Arrays entgegen und platziert ihn in der Eigenschaft selectedFlug.

Datenbindung verwenden

Um ein Model beziehungsweise ein ViewModel, das ein Controller über seinen Scope bereitstellt, an eine View zu binden, ist Letztere mit dem Controller zu verknüpfen. Dazu verwendet der Entwickler das von AngularJS verwendete Attribut ng-app (Name des Moduls) und ng-controller (Name des Controllers).

Diese vom Framework bereitgestellten Attribute, die zudem mit auszuführenden JavaScript-Routinen verknüpft sind, werden als Direktiven bezeichnet. Innerhalb des Elements, das mit ng-controller versehen ist, kann der Entwickler auf den vom Controller bestückten Scope zugreifen:

<div ng-app="flug">
<div ng-controller="FlugBuchenCtrl">
[...]
</div>
</div>

Die Direktive ng-show legt fest, ob ein Element angezeigt werden soll. Wird der damit referenzierte Wert als true ausgewertet, zeigt die Anwendung das Element, auf das ng-show angewandt wurde, an – sonst nicht. In Listing 2 verweist ng-show auf die Eigenschaft vm.message. Das führt dazu, dass das entsprechende div-Element nur zu sehen ist, wenn das hinter vm stehende ViewModel über die Eigenschaft message eine Nachricht für den Benutzer enthält. Der Bindungs-Ausdruck

{{ vm.message }}

bewirkt, dass die Nachricht ebendort ausgegeben wird. Bei den darauffolgenden, über das Element Input beschriebenen Textfeldern kommt die Direktive ng-model zum Einsatz, um die Eigenschaften flugNummerFilter, flugVonFilter und flugNachFilter des ViewModels an die Textfelder zu binden. Dabei handelt es sich um eine bidirektionale Datenbindung, das heißt, Änderungen an den Eigenschaften werden ans Textfeld weitergegeben und vice versa. Die Direktive ng-click des darauffolgenden Buttons verknüpft den Click-Handler mit der Methode loadFluege des ViewModels. Dabei ist zu beachten, dass ng-click nicht nur den Namen der Funktion, sondern den gesamten Funktionsaufruf repräsentiert. Da es sich bei loadFluege um eine Funktion handelt, die keine Argumente erwartet, stellt man dem Funktionsnamen zwei runde Klammern nach. Eventuelle Parameter ließen sich, wie gewohnt, innerhalb dieser anfügen.

Weitere Direktiven

Neben ng-click bietet AngularJS weitere Direktiven, die es erlauben, Funktionen an JavaScript-Ereignisse zu binden. Darunter befinden sich zum Beispiel ng-change (Änderung des Werts eines Steuerelements), ng-blur (Steuerelement hat Fokus verloren), ng-focus (Steuerelement hat Fokus erhalten) oder ng-checked (Checkbox wurde aktiviert beziehungsweise deaktiviert). Einen Überblick über sämtliche Direktiven hält die Dokumentation von AngularJS bereit.

Die Tabelle in der zweiten Hälfte des Listing 2 verwendet die beschriebene Direktive ng-show. Sie legt fest, dass die Anwendung die Tabelle nur anzeigt, wenn das Array fluege mindestens einen Eintrag enthält. Statt des Größer-Symbols (>), das in HTML ein reserviertes Zeichen darstellt, kommt die HTML-Entität &gt; (gt für greater than) zum Einsatz.

Die Direktive ng-repeat wiederholt das tr-Element der Tabelle für jeden Eintrag im Array fluege des ViewModels. Der darin hinterlegte Ausdruck legt fest, dass der jeweils behandelte Flug in der Variable f abzulegen ist und dass sich der Index, den der Flug im Array einnimmt, über die Variable $index in Erfahrung bringen lässt.

Zusätzlich bewirkt die Direktive ng-class, dass das tr-Element mit der CSS-Klasse selected zu formatieren ist, wenn die ID des gerade behandelten Flug-Objekts der ID des Flug-Objekts in der Eigenschaft selectedFlug entspricht. Das hat zur Folge, dass die Anwendung das markierte Objekt hervorgehoben darstellt. Unter Verwendung von entsprechenden Ausdrücken bindet danach die Anwendung die Eigenschaften ID, Datum, Abflugort und Zielort des jeweiligen Fluges an den Inhalt der einzelnen Zellen.

Die Direktive ng-click verknüpft die Methode selectFlug des ViewModels mit dem Click-Ereignis eines Links mit der Beschriftung "Auswählen". Dabei wird festgelegt, dass der jeweilige Wert der Variable $index an selectFlug zu übergeben ist. Damit sich der Link in jedem Browser anklicken lässt, ist er mit einem href-Attribut auszustatten. Es hat im betrachteten Fall den Wert javascript:void(0). Dies bewirkt, dass keine Aktion ausgeführt wird, wodurch das Programm bei einem Klick lediglich die hinter dem Click-Handler stehende Funktion ausführt.