9. Angular

Angular hat in den letzten Jahren eine rasanten Aufstieg erlebt. Angular ist ein Komponenten-Framework. Es ist ein Fundament für alle Applikationsarten, die auf HTML aufsetzen. Das sind natürlich Web-Anwendungen, können aber auch Apps für Smartphones sein.

Im Gegensatz zu jQuery handelt es sich um ein vollständiges Framework, dass alle Aspekte der Entwicklung abdeckt.

9.1 Einführung in Angular

Angular ist keine Weiterentwicklung von Angular 1. Stattdessen wurde ein völlig neuer Ansatz der Applikationsentwicklung entworfen. Während Angular 1 ein sauberes Entwurfsmuster (MVC = Model View Controller) für kleine und mittlere Anwendungen bietet, können mit Angular 2 umfassende, komplexe und sich zügig entwickelnde Applikationen erstellt werden. Die Entwicklung erfolgt komponentenbasiert.

Angular gibt es in zwei Versionen: AngularJS, das als Version 1 gilt und Angular, das ab Version 2 als fortlaufend aktualisiertes Rahmenwerk eigenständig existiert. Geplant sind zwei Hauptversionen pro Jahr, wobei Mitte 2017 Version 4 erschienen ist. Die folgenden Aussagen gelten ab Angular 2.

9.2 Die Programmiersprache

Erstmals seit vielen Jahren taucht in der Web-Welt etwas auf, was bislang unbekannt war – die Wahl einer Programmiersprache. Natürlich läuft im Browser weiterhin nur JavaScript. Aber mit ECMAScript6 (ES 2015) steht ein weiterer Dialekt bereit und die Programmierung erfolgt oft in TypeScript, was wiederum in JavaScript übersetzt wird. Angular wurde in TypeScript entwickelt. Es liegt also nahe, TypeScript auch bei der eigenen Anwendungsentwicklung zu benutzen. Natürlich muss sichergestellt werden, dass in der Entwicklungsumgebung TypeScript in JavaScript transpiliert wird. Visual Studio macht dies automatisch. Ansonsten eignen sich diverse Module aus npm (node package manager) dazu, den Prozess zu automatisieren. Das ist der einzige Nachteil – du benötigst einen Build-Prozess. Dazu findest du mehr im Abschnitt Werkzeug und dort speziell zu Gulp.

Angular nutzt zwingend TypeScript. Es geht zwar rein technisch auch mit reinem JavaScript, in der Praxis ist diese Vorgehensweise aber bedeutungslos und verringert außerordentlich die Produktivität. Wenn du noch nicht TypeScript gelernt hast, dann lies das entsprechende Kapitel in diesem Buch zuerst. Es lohnt sich!

9.3 Ein triviales Beispiel

Angular 2 benutzt als Entwurfsmuster Komponenten. Die Applikation selbst ist eine Komponente. Für ein einfaches Beispiel bietet sich der Name “HalloApp” an. Die Darstellung erfolgt über Tags. Generell werden hier viele Elemente benutzt, die nichts mit HTML zu tun haben. Es handelt sich praktisch um eine neue Markup-Sprache zur Beschreibung von Komponenten. Ein bisschen HTML ist am Ende natürlich immer dabei. Der Browser bekommt davon nichts mit – Angular erstellt HTML 5 aus den Bausteinen, die Sie erstellen.

Die Applikation sieht nun folgendermaßen aus:

1 <hallo-app>Laden...</hallo-app>

Dieser Teil wird in die HTML-Seite eingebaut. Im statische HTML steckt nun eine aktive Komponente. Diese muss nun erstellt werden.

Komponenten als Klassen

Basis ist eine Klasse. Klassen sehen generell folgendermaßen aus:

1 class HalloApp {
2 }

Dies ist eine syntaktische Vereinfachung der bekannten prototypischen Vererbung. Nach dem Übersetzen in JavaScript (oder beim Blick in die interna von ES2015) zeigt sich, dass hier weiter Prototypen am Werke sind.

Die Bausteine von Angular 2 sind stark modularisiert. Die benötigten Teile werden mittels import bereitgestellt:

 1 import {Component} from 'angular2/core';
 2 import {bootstrap} from 'angular2/platform/browser';
 3 
 4 @Component({
 5   selector: 'hallo-app',
 6   template : `<h1>
 7                 Hallo App
 8               </h1>`
 9 })
10 export class HalloAppComponent {
11 }
12 
13 bootstrap(HalloAppComponent);

Die fachliche Logik wird nun in einer Klassen geschrieben, aus der die Komponente mittels Annotation entsteht. die Annotation ist hier @Component (Zeile 4). Beschrieben wird hier das Tag (selector) und die HTML-Vorlage. Danach wird wird Kompoente exportiert und dann sofort gestartet (bootstrap).

Bindungen

Die bidirektionale Bindung war eines der augenfälligsten Effekte in Angular 1 und hat – ähnlich wie die Animationen in jQuery – erheblich zur Verbreitung beigetragen. Allerdings gab es bei größeren Anwendungen manchmal Probleme mit der Performance. Sehr viele nachlässig erstellte Bindungen benötigen einiges an Verarbeitungsleistung und wirken vor allem auf mobilen Geräten eher negativ.

In Angular 2 können Bindungen feingranularer erstellt werden. Sowohl Eigenschaften als auch Ereignisse lassen sich binden. Explizite Direktiven sind nicht mehr erforderlich. Als generische Syntax werden verschiedene Klammerformen benutzt.

 1 import {Component} from 'angular2/core';
 2 
 3 @Component({
 4     selector: 'hallo-app',
 5     template: `
 6     <input 
 7       type="text"
 8       (keyup)="onKeyUp()"
 9       (input)="color=event.target.value"
10       [style.background-color]="color"
11     >`
12 })
13 export class HalloAppComponent {
14     public color: string;
15     onKeyUp() {
16         console.log('keyup: ' +         
17         this.color)
18     }
19 }

In diesem Beispiel werden zwei unidirektionale Bindungen erstellt. () liest Werte, während [] schreibt. Dies lässt sich kombinieren, sodass eine bidirektionale Bindung mit [()] bezeichnet wird.

Die Bindung von Direktiven

Direktiven sind elementare Bausteine, die die Kernfunktionalität von Angular bereitstellen. Diese werden noch ausführlich behandelt. Auch Kernfunktionen müssen importiert werden. Eine wichtige Direktive zur Bindung ist die Zuweisung an ein Model – ngModel.

 1 import {Component} from angular2/core;
 2 
 3 @Component({
 4     selector: 'hallo-app',
 5     template : `
 6     <h1>
 7         Angular 2 Hallo App</h1>
 8     <form>
 9         <input 
10            type="text"
11            [(ngModel)]="search" >
12      <p>
13          Suchwert ist {{search}}
14      </p>
15     </form>
16 `
17 })
18 export class HalloAppComponent {
19 }

Das Formular enthält hier ein Eingabefeld mit bidirektionaler Bindung. Änderungen am Model – hier search – werden sowohl gelesen als auch geschrieben. Die Direktive sorgt für den Transport. Im Text der Vorlage erfolgt der Zugriff wie bei Angular 1 mit Hilfe der Handlebar-Syntax ({{}}).

Bindungsausdrücke

Bindungsausdrücke dienen dazu, Daten zu manipulieren. Die Ausdrück sind pures JavaScript.

1 {{search.toUpperCase() + "!"}}
2 {{1 + 2 + 3}}

Beachten Sie, dass komplexe Ausdrücke dazu führen, dass Logik in die Vorlagen wandert. Das ist keine gute Idee und deshalb sollten Ausdrücke einfachen Ausgabenaufbereitungen vorbehalten bleiben.

9.4 Direktiven und Komponenten

Direktiven und Komponenten sind eng verwandt. Beides sind Definitionen für eigene Elemente bzw. Attribute, die eien bestimmte Funktionalität bezeichnen. Komponenten sind Träger der Anwendungslogik, während Direktiven elementare und oft generische Funktionen bereitstellen.

Direktiven

Direktiven gibt es in zwei Ausführungen:

Komponenten

Komponenten sind der Kern eine Anwendung. Eine Komponente verknüpft eine Vorlage (template) mit einer Klasse über ein eigenes Element.

 1 import {Component} from 'angular2/core';
 2 
 3 @Component({
 4     selector: 'hallo-app',
 5     template: `
 6     Inhalt der Komponente
 7     `
 8 })
 9 export class HalloAppComponent {
10     // 
11 }

Der Selektor ist wie CSS zu lesen; er bezeichnet die Art der Erkennung in der HTML-Seite.

1 <body>
2   <div class="container">
3     <pizza-app>
4     </pizza-app>
5   </div>
6 </body>

Strukturelle Template Direktiven

Wie schon erklärt sollten strukturelle Direktiven immer dann verwendet werden, wenn der DOM verändert wird, sprich Elemente hinzugefügt oder entfernt werden sollen. Ein Beispiel dafür ist die ngIf-Direktive.

<button (click)=”isVisible = !isVisible”>anzeigen | verstecken</button> <div *ngIf=”isVisible”>Wir sind Ihr Pizza-Dienstleister!</div> Die Variable isVisible wird als Boolean-Wert interpretiert. Falls diese auf true bzw truthy steht, wird der div-Knoten in den DOM eingehangen, andernfalls entfernt.

Wie wir bereits gelernt haben, gibt es Event- und Property-Bindings in Angular 2. Ihre Anwendung kann durch die Verwendung von Klammern - () und [] - gesteuert werden. Im Falle einer strukturellen Direktive nutzen wir hierbei das *-Symbol.

Das * Symbol in Angular 2

Das Asterisk-Zeichen stellt die Kurzschreibweise einer strukturellen Direktive dar. Sie stellt auch automatisch eine Datenbindung her.

1 <pizza-list-item *ngFor="let pizza of menu"></pizza-list-item>

Strukturelle Direktiven würden im erweiterten Syntax den eigenen Quellcode sehr aufblähen. Das template-Tag gibt an, dass nachfolgend ein Angular-Template folgt, welches über die Bedingung entfernt oder hinzugefügt wird.

Intern wandelt Angular jedoch immer die Kurzschreibweise in die ausführliche um!

Attribut-Direktive

Wie der Name schon sagt, werden diese Direktiven als Attribut an ein DOM-Element geschrieben und können dessen Aussehen und/oder Verhalten verändern. Als einfaches Beispiel setzen wir die Schriftfarbe eines Elementes via einer Attribut-Direktive.

<div [style.color]=”‘red’“>Wir sind Ihr Pizza-Dienstleister!</div> Tip: Die eingebaute Direktive ngStyle sollte erst benutzt werden, wenn mehrere Style-Attribute gesetzt werden. Unser Beispiel würde jedoch mit ngStyle, wie folgt aussehen.

<div [ngStyle]=”{‘color’: ‘red’}”>Wir sind Ihr Pizza-Dienstleister!</div> Eigene Attribut-Direktive

Als kleines Beispiel schreiben wir nun für das Ändern der Schriftfarbe eine eigene Direktive.

 1 import {Directive, ElementRef, Renderer} from 'angular2/core';
 2 
 3 @Directive({
 4     selector: '[redFont]'
 5 })
 6 export class RedFontDirective {
 7     constructor(el: ElementRef, renderer: Renderer) {
 8         // el.nativeElement.style.color = 'red';
 9         renderer.setElementStyle(el.nativeElement, 'color', 'red');
10     }
11 }

Eine Direktive wird über den Decorator @Directive definiert. Als wichtigste Meta-Daten muss wieder ein selector angegeben werden, damit unsere Direktive überhaupt ausgeführt wird. Im Unterscheid zur @Component wird der Selektor in [] geschrieben, wodurch ein Attribut-Name definiert wird. Im Beispiel werden zwei wichtige Bestandteile für die Arbeit mit Direktiven der Angular 2-Bibliothek genutzt.

ElementRef - erlaubt Zugriff auf das verbundene DOM-Element Renderer - Framework zum performanten Ändern von DOM-Elementen

Es ist natürlich möglich das DOM-Element direkt selbst zu ändern. Das kann jedoch in vielen Situationen auf Kosten der Anwendungs-Performance passieren. Der Renderer ermöglich beispielsweise das Rendern an Web-Worker auszulagern. Benutzen von Komponenten und Direktiven

Damit eine Direktive oder Komponente überhaupt in einem Teil unser Anwendung genutzt werden kann, müssen diese der entsprechenden Komponente bekannt gemacht werden. Hierzu importieren wir die Direktive via import und übergeben dem @Component Decorater mit dem Parameter directives ein Array von Direktiven-Definitionen. Somit kann eine klare Abgrenzung geschaffen werden, welche Direktive wo benutzt werden kann und auch Naming-Kollisionen vermieden oder geschickt als Konfiguration genutzt werden.

 1 import {Component} from 'angular2/core';
 2 
 3 import {RedFontDirective} from '../directives/redFont.directive';
 4 
 5 @Component({
 6     selector: 'pizza-app',
 7     directives: [RedFontDirective],
 8     template: `
 9     <button (click)="isVisible = !isVisible" redFont>anzeigen | vers\
10 tecken</button>
11     <div *ngIf="isVisible" [style.color]="'red'">Wir sind Ihr Pizza-\
12 Dienstleister!</div>
13     `
14     })
15 export class PizzaAppComponent {
16     public isVisible:boolean = true;
17 }

Am Ende packen wir die Direktive - mit Hilfe des festgelegten Attributsnamen - an einen DOM-Knoten mit Text. Voila!

Schleifen mit NgFor

Wie bereits erfahren, existiert in Angular 2 natürlich eine Direktive, die das Wiederholen von DOM-Elementen erlaubt. Im Gegensatz zu AngularJS 1 heißt diese nicht ngRepeat, sondern ngFor. Als strukturelle Direktive wird diese an einen bestehenden DOM-Knoten, wie folgt gebunden.

1 <div *ngFor="let number of [1, 5, 34, 47]">
2     Aktuelle Zahl ist: {{number}}
3 </div>

Das *-Symbol gibt an, dass es sich um eine strukturelle Direktive handelt. Das aktuelle Element der Schleife wird auf eine neue lokale Variable number geschrieben. Die Definition einer Variable wird über das #-Symbol ausgezeichnet. Die Liste an Elementen kann dabei natürlich auch aus einer Variable kommen.

Ebenso, wie in AngularJS, kann auch in der zweiten Version des Frameworks auf den aktuellen Index der Schleife zugegriffen werden. Dazu erweitern wir unsere Quellcode ein wenig.

1 <div *ngFor="let number of [1, 5, 34, 47]; let currentIndex=index">
2     Aktuelle Zahl ist: {{number}} ({{currentIndex}})
3 </div>

Nach der Angabe der Liste kann der aktuelle Index auf eine eigene Variable geschrieben werden, um auf sie zugreifen zu können.

Pipes

In Angular 2 sind Pipes das, was in Angular 1 Filter waren. Sie erlauben das Transformieren von Daten in Ausdrücken. Pipes leiten Daten von den Ausdrücken weiter an eine Funktion, die die Daten manipuliert. Einige eingebaute Pipes sind bereits vorhanden. Die Anwendung erfolgt mit dem Pipe-Symbol (|):

1 <span>{{10.99 | currency}}</span>

Die Pipes haben oft Parameter:

1 <span>{{10.99 | currency:'EUR':true}}</span>
Eigene Pipes

Mit dem Dekorator @Pipe lassen sich eigene Pipes erstellen. Die Basisfunktion wird aus PipeTransform geerbt. Hier ein Beispiel:

1 import {Pipe, PipeTransform} from 'angular2/core';
2 
3 @Pipe({name: 'addTwo'})
4 export class UpperCase implements PipeTransform {
5     transform(text:string, args:string[]) : any {
6         return text.toUppercase();
7     }
8 }

Um dieses Pipe benutzen zu können, müssen Sie es bekannt machen:

 1 import {Component} from 'angular2/core';
 2 
 3 import {AddTwoPipe} from '../pipes/addTwo.pipe';
 4 
 5 @Component({
 6     selector: 'pizza-app',
 7     pipes: [AddTwoPipe],
 8     template: `
 9         <span>{{10.99 | currency}}</span>
10         <span>{{10.99 | currency:'EUR':true}}</span>
11         <div>{{1 | addTwo}}</div>
12     `
13 })
14 export class PizzaAppComponent {
15 }

Dienste

Wiederverwendbare Bestandteile der Applikation ohne Beug zur UI werden mittels Diensten (services) implementiert. Dabei gibt es zwei Arten von Diensten:

Dienste werden mittels Dependency Injection bereitgestellt. Sie werden damit bei der Benutzung injiziert, also quasi von außen bereitgestellt. Die nutzende Seite hat keine Informationen über Herkunft und Konstruktion des Dienstes. Die Annotation @Injectable dient dazu, Dienste zu kennzeichnen.

 1 import {Injectable} from '@angular/core';
 2 
 3 @Injectable()
 4 export class PizzaService {
 5     getPizza() {
 6         return [{
 7             "id": 1,
 8             "name": "Pizza Vegetaria",
 9             "price": 5.99
10         }, {
11             "id": 2,
12             "name": "Pizza Salami",
13             "price": 10.99
14         }, {
15             "id": 3,
16             "name": "Pizza Thunfisch",
17             "price": 7.99
18         }, {
19             "id": 4,
20             "name": "Aktueller Flyer",
21             "price": 0
22         }]
23     }
24 }

Um einen Dienst zu nutzen, wird die Eigenschaft providers der Komponente gesetzt (Zeile 7):

 1 import {Component} from '@angular/core';
 2 
 3 import {HalloService} from '../services/hallo.service';
 4 
 5 @Component({
 6     selector: 'hallo-app',
 7     providers: [HalloService],
 8     template: `
 9     <span>Anzahl an Pizzen: {{pizzas.length}}</span>
10     `
11 })
12 export class PizzaAppComponent {
13     public pizzas = [];
14 
15     constructor(private DeliveryHero: PizzaService) {
16         this.pizzas = this.DeliveryHero.getPizza();
17     }
18 }

Durch die Angabe des Services als Provider der Component, wird beim Erstellen der Komponente eine neue Instanz des Services erzeugt. Diese ist auch nur für diese Komponente und ihre Kind-Komponenten, welche diesen Service gegebenenfalls auch benutzen, verfügbar.

Soll ein Service global - sprich anwendungsweit - verfügbar sein, kann dieser in der Hauptkomponente der Anwendung oder bereits zum App-Start in der bootstrap-Methode geladen und verfügbar gemacht werden.

bootstrap(AppComponent, [PizzaService]);

HTTP in Angular 2

Ein wichtiger Bestandteil von Web-Anwendungen ist die Kommunikation mit Schnittstellen. Typischerweise basieren diese Schnittstellen auf dem HTTP-Protokoll. Für diesen Zweck existiert, wie schon in AngularJS 1, ein HTTP-Service. Für die Kommunikation mit einer Schnittstelle sollte ein eigener Service angelegt werden. Aus diesem Grund wandeln wir nun unseren Pizza-Service so ab, dass er die Angebots-Daten aus einer JSON-Datei abfragt. Diese wird über eine GET-Anfrage abgerufen. Diedann in das JSON-Format umgewandelt, um damit in der Anwendung umgehen zu können.

 1 import {Http} from '@angular/http';
 2 import {Injectable} from '@angular/core';
 3 import 'rxjs/add/operator/map'; // add map function to observable
 4 
 5 @Injectable()
 6 export class PizzaService {
 7     constructor(private http: Http) {
 8     }
 9 
10     getPizza() {
11         return this.http('assets/pizza.json')
12             .map(response => response.json());
13     }
14 }

Zuerst wird der Http-Service von Angular 2 importiert und dann über die Dependency-Injection dem Service bereitgestellt. Die Funktion getPizza() kann dann innerhalb einer Komponente aufgerufen werden, um die Daten abzurufen. Ein Request läuft asynchron, daher liefert der Http-Service ein so genanntes Observable zurück, welches über die RxJS-Bibliothek erzeugt wird.

In der aktuellen Beta muss daher die map-Funktion erst explizit geladen werden, damit sie auf dem Observable ausgeführt werden kann! Unsere PizzaAppComponent wird nun den neuen PizzaService benutzen.

Observables in Angular 2

Ein Observable ist mit JavaScript-Promises vergleichbar. Ist der Programmcode des Observable abgeschlossen, wird allen Abonnenten Bescheid gegeben. Auf diesen Observables basiert auch das Event-System von Angular 2 (Stichwort EventEmitter).

Um ein Observable zu abonnieren, muss dessen subscribe-Funktion aufgerufen werden. Als Callback erhält diese eine Funktion, welche wiederum als Parameter geänderte oder neue Daten erhält. In unserem Fall sind das, die Pizzen aus der JSON-Datei.

 1 import {Component} from 'angular2/core';
 2 import {HTTP_PROVIDERS} from 'angular2/http';
 3 
 4 import {PizzaService} from '../services/pizza.service';
 5 
 6 @Component({
 7     selector: 'pizza-app',
 8     providers: [PizzaService, HTTP_PROVIDERS],
 9     template: `
10     <span>Anzahl an Pizzen: {{pizzas.length}}</span>
11     `
12 })
13 export class PizzaAppComponent {
14     public pizzas = [];
15 
16     constructor(private pizzaService: PizzaService) {
17         this.loadData(); 
18     }
19 
20     loadData(){
21          this.pizzaService.getPizza()
22                             .subscribe(pizzas => this.pizzas = pizza\
23 s);
24     }
25 }

Lebendauer der Komponenten

Eine Komponente in Angular durchläuft verschiedene Zustände während der Ausführung. Diese werden auch Lebenszyklen genannt. Über die Lifecycle-Hooks können wir hier an verschiedenen Stellen eingreifen. Folgende Funktionen können dazu genutzt werden:

Das Beispiel zur Verwendung des Http-Services wird nun so erweitert, dass die Pizzen nicht direkt im Konstruktor der PizzaAppComponent abgerufen werden, sondern erst wenn die Komponente initialisiert wurde.

 1 import {Component} from '@angular/core';
 2 import {HTTP_PROVIDERS} from '@angular/http';
 3 
 4 import {PizzaService} from '../services/pizza.service';
 5 
 6 @Component({
 7     selector: 'pizza-app',
 8     providers: [PizzaService, HTTP_PROVIDERS],
 9     template: `
10     <span>Anzahl an Pizzen: {{pizzas.length}}</span>
11     `
12 })
13 export class PizzaAppComponent {
14     public pizzas = [];
15 
16     constructor(private pizzaService: PizzaService) {
17     }
18 
19     ngOnInit() {
20         this.pizzaService.getPizza().subscribe(pizzas => this.pizzas\
21  = pizzas);
22     }
23 }

9.5 Architektur einer Angular-Anwendung

Eine Angular-Anwendung erfordert eine stringente und gut geplante Architektur, andernfalls kommt es schnell zu einer verwirrenden Sammlung von Code-Schnippels, die kaum wartbar sind. Die Sprachmerkmal von TypeScript und die Modularisierung von Angular helfen dabei und unterstützen die Vorgehensweise explizit.

Bestandteile der Anwendung

Technisch kann eine Anwendung in folgende Bausteine zerlegt werden:

Die Komponenten lassen sich weiter aufteilen. Zum einen sind dies universelle – meist als Widget bezeichnete – UI-Elemente, wie beispielsweise Tabellen (Grid), Baumansichten (Treeview) oder Tabulatoren (Tabs). Zum anderen sind dies die elementaren Bausteine der Applikation, die mit dem Benutzer interagieren. Das sind dann Formulare, Dashboards usw.

Bei den Konfigurationen werden alle globalen Dinge abgelegt. Dies betrifft vor allem das Routing und Mehrsprachigkeit. Dienste dienen der Kommunikation zwischen Komponenten und der Bereitstellung einer Dienstschicht zum Server. Modelle fassen alle Klassen zusammen, die Daten behandeln. Im Wesentlichen sind dies View-Modelle und deren Hilfsbausteine wie Validatoren.

Benennungsregeln

Lege dir am Anfang klare Benennungsregeln auf. Die vielen Formen machen es sonst schwer, die richtigen Dateinamen zu finden. Generell sollte der Name der Datei dem Namen der Klasse entsprechen.

Klassen können einen Suffix bekommen, wenn es sehr viele einer Sorte gibt. Das vermeidet Konflikte und damit unglücklich gewählte Namen:

Auf Dateiebene bietet es sich eher an, Präfixe zu verwenden. Das hat praktische Gründe, auch wenn es auf den ersten Blick inkonsequent erscheint. Dateilisten sind oft alphabetisch sortiert, und dann hast du die Gruppen schnell im Blick, auch wenn sie im Client als lange Liste von JS-Dateien ankommen.

Komponenten

Komponenten bilden am Ende eine Baumstruktur, viele universelle Komponenten sind aber mehrfach im Einsatz. Ein reiner Baum eignet sich deshalb nicht zur Anordnung der Komponenten im Projekt. Eine fachliche Anordnung ist oft sinnvoller. Als Beispiel soll hier eine Applikation beschrieben werden, die Veranstaltungen verwaltet. Diese hätte dann folgende Komponenten-Struktur:

 1 App -
 2     |- Compontents
 3         |- Widgets
 4             |- DataGrid 
 5               | - Models
 6               |  |- datagrid.helper.ts  
 7               |  |- datagrid.model.ts               
 8               |- pagination.component.ts               
 9             |- Treeview  
10               | - Models
11               |  |- index.ts  
12               |  |- vm-treeview-baseinterface.ts               
13               |  |- datagrid.helper.ts  
14               |  |- datagrid.model.ts               
15               |- treeview.ts  
16               |- treeview-node.ts  
17             |- infobox.ts  
18             |- sidemenu.ts  
19             |- tabs.ts  
20             |- webpart.ts  
21         |- events
22             |- list.ts
23             |- new.ts
24             |- edit.ts
25             |- delete.ts
26         |- users 
27             |- list.ts
28             |- new.ts
29             |- edit.ts
30             |- delete.ts
31     |- Configurations
32         |- routes.ts
33     |- Decorators
34         |- diverse Dekoratoren 
35     |- Services 
36         |- diverse Dienste
37     |- Utils
38         |- diverse Hilfsklassen 
39     |- ViewModels        
40         |- EventViewModel.ts 
41         |- UserViewModel.ts

Oft sind viele Komponenten in einem Rutsch zu importieren. Wenn Sie zwei oder mehr in einem Ordner haben, fügen Sie immer eine Datei index.ts ein:

1 export * from './TreeView/ac-treeview';
2 export * from './TreeView/ac-treeview-node';
3 export * from './ac-infobox';
4 export * from './ac-breadcrumb';
5 export * from './ac-webpart';
6 export * from './DataGrid/ac-datagridpagination';
7 export * from './ac-tabs';
8 export * from './ac-sidemenu';

Beim Import einer oder mehrerer Komponenten sieht es dann folgendermaßen aus:

1 import * as cmp from './Components/index';
2 import * as wd from './Components/Widgets/index';

Sie können dann mit cmp.ComponentenName oder wd.WidgetName auf die Komponenten zugreifen, ohne jede Datei einzeln importieren zu müssen. Die Dateierweiterung *.ts wird automatisch benutzt, sodass es reicht index zu schreiben.

9.6 Grundlagen Redux

Redux ist eine Bibliothek zur Verwaltung von Zuständen. Sie ist unabhängig von Angular und von jedem anderen Framework. Das Zusammenspiel mit Angular ist jedoch besonders spannend und hier behandelt. Zuerst werden die Grundlagen isoliert betrachtet, um den Einstieg so einfach wie möglich zu halten.

Einführung

Dieser Abschnitt behandelt die Grundidee hinter Redux.

Ein Zustandsbaum

Es ist fundamental für Redux, dass alle Zustände einer Applikation ein einem zentralen JavaScript-Objekt gehalten werden. Üblicherweise ist dies eine Objektstruktur – der Zustandsbaum. Dieses Objekt wird auch als Store oder Application-Store bezeichnet. Die Zustände in diesem Objekt sind grundsätzlich unveränderlich (immutable). Um eine Zustandsänderung durchzuführen, werden sogenannte Reducer benutzt. Das sind Funktionen zum Verändern der Zustände.

Die Unveränderlichkeit und die Konsolidierung der Zustandsdiagramme einer Applikation erleichtert das Verständnis und die Vorhersagbarkeit der Reaktionen in einer Applikation um einiges leichter.

Der Ereignisfluss

In Redux werden Benutzeraktionen gefangen und an Reducer zur Verarbeitung übertragen. Die Last der Eingabeverarbeitung liegt damit nicht mehr beim Controller. Viele Benutzeraktionen führen lediglich zu Zustandsänderungen. Diese Vorgänge sind nicht unbedingt als Teil der Geschäftslogik zu betrachten. Die Trennung vereinfacht die Controller und damit die Komponenten der Applikation.

Ereignisse fließen in der Applikation nach oben, vom DOM zur Logik. Werden Komponenten verschachtelt, fließen Ereignisse von der Kindkomponente zur Elternkomponente. Werden Dienste benutzt, beispielsweise zum asynchronen Abruf von Daten von einem Server, werden die Ergebisse als Ereignis ausgesendet und an den Reducer geliefert.

Der Zustandsfluss

Zustände fließen nach unten, also entgegen den Ereignissen. Die Zustände in einer Elternkomponente wirken also auf die Kindkomponenten, wenn dies erforderlich ist, jedoch niemals umgekehrt.