Part 4 – AngularJS 2/2

27. August 2013

Im vorherigen Artikel haben wir schon begonnen uns mit AngularJS auseinander zu setzen. Wir haben die ersten Erfahrungen gemacht und festgestellt, dass es eine Menge zu Lernen gibt. Zu den neuen Konzepten der Services und der Möglichkeit der Dependency Injection, gibt es auch altbekannte Muster, wie das Data Binding und das Templating. Im Großen und Ganzen denke ich, dass der Aufwand überschaubar ist und dafür später durch eine flexible Anwendung belohnt wird.

Die Anwendung

Nachdem unsere todoApp im letzten Artikel definiert und auch das HTML-Grundgerüst erstellt wurde, wird es Zeit, sich um die eigentliche Implementierung zu kümmern. Beginnen wir mit den todoApp.services (app/js/services.js):

   1: var module = angular.module('todoApp.services', []);

   2: module.factory('todoService', function(){

   3:     var todos = {};

   4:     todos.get = function(){ 

   5:         return [

   6:             {description: "Müll raus bringen", done: false},

   7:             {description: "Kinokarten kaufen", done: false}

   8:         ];

   9:     };

  10:     return todos;

  11: });

Analog zu unserem todoApp-Modul erzeugen wir unser todoApp.services-Modul. Das todoApp.services-Modul besitzt keine Abhängigkeiten zu anderen Modulen, daher ist das Array für Abhängigkeiten leer.

Im erzeugten Service-Modul erzeugen wir mithilfe der factory(name,function/array) Funktion unseren todoService. Der todoService hat keine Abhängigkeiten zu anderen Modulen, somit übergeben wir kein Array, sondern einfach die Funktion, die den Service erzeugt. Alternativ hätte man auch ein Array, mit der Funktion als einziges Item, übergeben können. In AngularJS sind Services immer Singletons und werden per Lazy loading erzeugt.

Nachdem wir den todoService erstellt haben, wird es Zeit, unser todoApp.controllers-Modul zu erstellen (app/js/controllers.js):

   1: var module = angular.module('todoApp.controllers', []);

   2: module.controller('TodoController', 

   3:             ['$scope', 'todoService', function(scope, todoService, guid) {

   4:     scope.todos = todoService.get();

   5:     scope.query = "";

   6:     scope.newTodo = "";

   7:     scope.addNew = function(){

   8:         scope.todos.push({description: scope.newTodo, done: false});

   9:         scope.newTodo = "";

  10:     };

  11: }]);

  12: module.controller('AboutController', ['$scope',function(scope) {

  13:     scope.aboutMessage = "Kleine Anwendung zum verwalten von Todos";

  14: }]);

Wie man sieht, kommt man an Modulen nicht vorbei. Wir erzeugen das todoApp.controllers-Modul und nutzen die controller(name,function/array) Funktion des Moduls, um unseren TodoController und AboutController zu erstellen. Der erste Parameter ist der Name des Controllers. Der zweite Parameter ist die Funktion bzw. das Array, die bzw. das beim Erstellen des Controllers verwendet wird (Konstruktor).

Unser TodoController hat zwei Abhängigkeiten. Die eine Abhängigkeit ist unser gerade erstellter todoService. Die andere Abhängigkeit wird von Controllern so gut wie immer benötigt: $scope.

Mit dem $scope bekommen wir Zugriff auf den aktuellen Ausführungskontext des Controllers. $scope kann zum Beispiel in Callback-Funktionen eines Controllers verwendet werden, um Zugriff auf die Eigenschaften oder Funktionen des Controllers zu bekommen.

In unserer Konstruktor-Funktion werden alle Eigenschaften oder Funktionen, die später in der View zum Data Binding benötigt werden, dem scope hinzugefügt. Zusätzlich initialisieren wir hier die Daten. Dafür nutzen wir den todoService und rufen die Todo-Einträge ab. Diese weisen wir der todos Eigenschaft des TodoControllers zu.

Die query Eigenschaft dient der im vorherigen Beitrag erwähnten Filterfunktion. Dazu aber gleich noch mehr.

Der AboutController ist nur zu Demonstrationszwecken gedacht, um eine zweite Seite anzubieten. Daher hat dieser auch keine Abhängigkeiten und nur die eine statische Eigenschaft aboutMessage.

Nachdem wir nun unsere Controller haben, wird es Zeit, sich um die Views zu kümmern. Dafür erstellen wir zwei Views, auf die wir in unserer Navigation schon verwiesen haben.

Beginnen wir mit der ersten und wichtigsten View (app/partials/todos.html):

   1: <div id="todos-page" class="container-fluid">

   2:   <div class="row-fluid">

   3:       Filter: <input ng-model="query" placeholder="Filter die Einträge .."></input>

   4:       <ul class="todos">

   5:         <li ng-repeat="todo in todos |filter: query" class="">

   6:           <input type="checkbox" ng-model="todo.done" />

   7:           <span class="todo-{{todo.done}}">{{$index + 1}} - {{todo.description}}</span>

   8:         </li>

   9:       </ul>

  10:       <div class="input-append">

  11:           <input class="input-medium" type="text" ng-model="newTodo" placeholder="Neuer Todo Eintrag .." />

  12:           <button class="btn" ng-click="addNew()">Add</button>

  13:       </div>

  14:   </div>

  15: </div>

Hier sehen wir unsere Partial-View für die Darstellung der Todo-Einträge. Neben den bekannten HTML-Elementen und Attributen, haben wir auch hier wieder eine ganze Reihe von AngularJS spezifischen Attributen.

Beginnen wir mit ng-model:

ng-model wird verwendet um Two-Way-Binding zwischen der View und dem Model herzustellen. Mit Model meint man hier eine Eigenschaft des Controllers. Angewandt wird ng-model auch nur auf Eingabefelder wie input, select und textarea. Interessant zu wissen ist die Tatsache, dass AngularJS das jeweilige Model erstellt, sollte es nicht schon im Controller $scope enthalten sein. Allerdings finde ich es recht unübersichtlich, wenn auf einmal Eigenschaften des Controllers vom View erzeugt werden. Daher erzeuge ich für eine bessere Wartbarkeit immer alle Eigenschaften, die später in der View benutzt werden, im Controller.

Als Nächstes betrachten wir ng-repeat:

ng-repeat ist im Prinzip eine foreach-Schleife und erzeugt ab dem Element, auf dem ng-repeat eingesetzt wird, einen neuen Scope. Der neue Scope zeigt auf das aktuell in der Schleife befindliche selektierte Element der Liste. Von der Sache her ist das ähnlich zu dem foreach aus knockoutJS. Alle HTML-Elemente, die zwischen diese Schleife geschrieben werden, dienen auch hier als Template für das gerade selektierte Element der Liste.

Kommen wir nun zum Filter:

Wie im HTML-Snippet schon erkennbar, wurde hinter die foreach-Schleife per Pipe-Symbol ein Filter angehängt. Die Filter Funktion ist Bestandteil von AngularJS und filtert ein Array auf bestimmte Werte. Damit kann in unserer Anwendung mit einfachen Mitteln eine Filterung über die verfügbaren Todo-Einträge gemacht werden. Die Filterfunktion kann entweder einen String, ein Objekt oder eine Funktion als Filterkriterium nutzen. In unserem Fall ist es ein String. Nämlich der String, der in die query-Eigenschaft geschrieben wird.

Als Letztes möchte ich hier noch auf die curly brace {{ … }} Syntax eingehen. Das ist die Template Syntax, um dynamisch HTML zu erzeugen und zusätzlich ein One-Way-Binding zwischen Controller-Eigenschaften und HTML-Elementen herzustellen. Ändern sich hier die Werte der Controller-Eigenschaft, wird das HTML-Element automatisch mit dem aktuellen Wert aktualisiert. Diese Syntax kann quasi überall in der Seite eingesetzt werden. Damit lässt sich zum Beispiel auch das class-Attribut dynamisch ändern, sobald sich die done-Eigenschaft eines Todo-Eintrags ändert.

Als Nächstes erstellen wir noch schnell die About Seite (app/partials/about.html):

   1: <div>

   2:   <p>About Stuff</p>

   3:   <div>

   4:       {{aboutMessage}}

   5:   </div>

   6: </div>

Hier gibt es nix neues, daher gehe ich gleich weiter im Text und wir schauen uns die fertige Anwendung mal im Browser an 🙂

Resultat

todolist   about

Im Ergebnis kein Unterschied zu der Version mit knockoutJS (abgesehen von dem CSS und der Navigation). Allerdings haben wir durch die modulare Aufteilung des Service, der Controller und der Views die Chance bekommen, auch wesentlich komplexere Anwendungen zu entwerfen, die sich am Ende immer noch gut warten lassen.

Ausblick und Fazit

Wir haben bis jetzt einen groben Einblick in die Möglichkeiten von AngularJS bekommen und haben auch ein Gefühl dafür bekommen, z.B. mehrere Views einzubinden. Im nächsten Beitrag werde ich noch auf das Testing eingehen, was durch den modularen Aufbau der Anwendung ermöglicht bzw. erleichtert wurde.

Stay tuned 😉