Angular ist die Komplettlösung für die Umsetzung von Webapplikationen im Frontend. Ein so umfassendes Werkzeug hat allerdings auch seine Schattenseiten: Die Einstiegshürde ist relativ hoch. Dieser Vortrag stellt die wichtigsten Elemente des Frameworks wie Komponenten, Direktiven und Services vor. In einem praktischen Beispiel werden die verschiedenen Elemente von Angular Schritt für Schritt zu einer kompletten Applikation zusammengefügt. Damit lernen Sie nicht nur die Elemente des Frameworks kennen, sondern gleichzeitig die wichtigsten Architekturpatterns und zahlreiche Best Practices.
4. Angular
Angular ist ein JavaScript-Framework, das von Google
entwickelt wurde. Es ist Open Source und wird auf GitHub
maintained.
Das Projekt selbst ist in mehrere Module unterteilt, sodass
man lediglich die Module laden muss, die für einen selbst
relevant sind.
Alle Module werden parallel entwickelt und wiesen die gleiche
Versionsnummer auf.
Angular setzt mittlerweile auf Semantic Versioning. Es gibt
zweimal im Jahr ein neues Major-Release
7. TypeScript
Das Entwickler Team von Angular hat sich dazu entschieden
ein Typsystem für JavaScript einzusetzen. Ursprünglich
wurde mit AtScript eine eigene Lösung entwickelt. Während
der Entwicklung wurde auf TypeScript geschwenkt.
TypeScript bietet statische Typenprüfung, die Verwendung
von Interfaces und einen Transpiler der moderne JavaScript-
Features auch für ältere Browser verfügbar macht.
8. TypeScript
Der TypeScript Compiler (tsc) wandelt den TypeScript-Code
in JavaScript um, das vom Browser ausgeführt wird.
Eine TypeScript-Datei endet per Konvention auf .ts.
Dem Compiler können über eine Konfigurationsdatei
(tsconfig.json) bestimmte Einstellungen mitgegeben werden.
Liegt einen Bibliothek nicht in TypeScript vor, können
Typdefinitionen heruntergeladen und installiert werden (http://
definitelytyped.org/)
9. TypeScript
Die Kernfeatures von TypeScript sind:
• Datentypen (primitive und composite)
• Typsichere Funktionen (Parameterliste und Rückgabewert)
• Klassen, Interfaces und Generics
10. TypeScript
import { Person } from './person';
export class User extends Person {
private password: string;
constructor(public name: string) {
super(name);
}
public setPassword(password: string) {
this.password = this.createHash(password);
}
private createHash(input: string): string {...}
}
12. CLI
Um den Start in eine Angular Applikation so einfach wie
möglich zu machen, gibt es das Angular-CLI-Tool.
Dieses Werkzeug kommt im gesamten Lebenszyklus zum
Einsatz von der Erstellung des Projekts, über die
Entwicklung des Quellcodes bis zum Bau der fertigen
Applikation für den Produktivbetrieb.
14. CLI
Die wichtigsten Kommandos:
Neues Projekt anlegen:
ng new <Projekt>
Neues Element (z.B. Component) erzeugen:
ng generate <blueprint> <Bezeichnung>
Dev Server starten:
ng serve
Applikation bauen
ng build
17. Module
Container-Element in der Applikation. Es fasst
zusammengehörige Components, Services, etc. zusammen.
Eine Applikation kann mehrere Module importieren.
18. Component
Baustein einer Applikation. Eine Component besteht aus
Metainformationen, einem Template und einer Klasse.
Eine Applikation setzt sich aus einem Baum von
Komponenten zusammen.
20. Directive
Directives wirken sich auf das Verhalten und die Struktur des
DOM aus. Sie kommen in den Templates einer Applikation vor.
Eine Component ist eine Sonderform der Directive, da sie ein
eigenes Template aufweist.
21. Pipe
Mit einer Pipe lässt sich eine gegebene Eingabe nach
bestimmten Regeln in einen Ausgabe transformieren.
Sie dienen vor allem der Anpassung von Anzeigewerten.
23. Module
Das App-Modul ist der Einstiegspunkt in die Applikation. Es
gibt an, welche weiteren Module importiert werden, welche
Components und Services verfügbar sind und welche
Component die Wurzel der Applikation bildet.
Das App Module wird initial vom CLI erzeugt
24. Module
import { … } from ‘…’;
@NgModule({
declarations: […],
imports: […],
providers: […],
bootstrap: [AppComponent]
})
export class AppModule { }
• declarations: Registrierung
von Components
• imports: Importieren von
Modulen
• providers: Registrierung von
Services
• bootstrap: Root Component
26. Component
Erzeugung:
ng generate component HelloWorld
Es wird ein Verzeichnis mit folgendem Inhalt erzeugt:
src/app/hello-world/
├── hello-world.component.css
├── hello-world.component.html
├── hello-world.component.spec.ts
└── hello-world.component.ts
Die Component wird automatisch im Module registriert
28. Component
Der grundlegende Aufbau einer Component ist:
• Imports aller benötigten Dateien
• Decorator mit Metadaten der Component
• Component Class
Der Selector gibt den Tag-Namen an über den die
Component eingebunden wird.
Die Component-Klasse dient der Datenhaltung für das
Template und stellt Methoden zur Verfügung.
30. Styling
Standardmäßig gelten die in einer Component definierten
Styles nur für die Component. Diese Lösung wird durch das
Umbenennen der CSS-Klassen gelöst.
Über die encapsulation-Eigenschaft des Decorators können
auch andere Modi verwendet werden:
• Native: Der Shadow-DOM wird verwendet
• Emulated: CSS Renaming (Standard)
• None: Keine Kapselung, Styles gelten global
32. Template
Angular nutzt HTML als Template-Engine. Templates können
entweder inline im Decorator einer Component definiert
werden oder in einer separaten HTML-Datei, was die
Empfohlene Lösung ist.
Per Interpolation ({{}}) kann vom Template aus auf die
Eigenschaften der Component-Klasse zugegriffen werden.
35. Event-Binding
Ähnlich wie auf Eigenschaften kann auch auf Methoden der
Component-Class zugegriffen werden.
(click)=“handleClick()”
Mit dem Event-Binding wird beim Auslösen des Events z.B.
eines Klicks die verbundene Methode der Component-Class
aufgerufen. Dieser Methode können Eigenschaften übergeben
werden.
Mit der $event-Variablen erhält die Methode eine Referenz auf
das Event-Objekt.
38. Lifecycle
Im Lebenszyklus einer Component gibt es verschiedene
Phase. Für jede dieser Phasen gibt es einen Hook, für den
man eine Funktion definieren kann.
Vorgehensweise:
• Interface implementieren
• Hook-Methode umsetzen
import { Component, OnInit } from '@angular/core';
@Component({…})
export class ListComponent implements OnInit {
ngOnInit() {
// initialization here
}
}
39. Lifecycle
Wird bei der Erzeugung ausgeführt
Auf eingehende Datenänderungen reagieren
Initialisierung (wird nur 1x aufgerufen)
Wird bei der Change Detection aufgerufen
Nachdem externer Inhalt verarbeitet wurde
Reaktionsmöglichkeit auf Inhaltsprüfungen
Reaktionsmöglichkeit auf Initialisierung
Hook nach View und View Children
Wird vor der Zerstörung aufgerufen
41. Child Components
Eine Angular-Applikation besteht aus einem Baum von
Components. In einem Template einer Component können
beliebig viele Child Components referenziert werden.
Diese Art der Strukturierung erhöht die
Wiederverwendbarkeit einzelner Teile der Appliktion.
Außerdem wird durch definierte Schnittstellen eine lose
Kopplung zwischen den einzelnen Components geschaffen.
42. Child Components
ng generate component list-item
Component auf der Kommandozeile erzeugen
Component in app.module.ts (declarations) registrieren
(automatisch)
Component verwenden (list.component.html)
<ul>
<app-list-item *ngFor="let item of items"></app-list-item>
</ul>
43. Child Component - Input
Um Informationen in eine Child Component hinein zu reichen,
wird das Angular Property Binding ([]) in Verbindung mit
Input Binding (@Input) verwendet. Achtung: auf @Input folgen
immer Klammern.
<ul>
<app-list-item *ngFor="let item of items" [item]="item">
</app-list-item>
</ul>
@Component({…})
export class ListItemComponent {
@Input()
item: Item;
constructor() { }
}
44. Child Component - Output
Die Informationen, die per Input-Binding in eine Child
Component hineingegeben werden, sollten nicht by
Reference verändert werden. Werden die Daten angepasst,
passiert dies auf einer Kopie der Daten und die Parent
Component wird per Event benachrichtigt.
1. Output Binding festlegen
2. Eventhandler erzeugen und emit-Methode aufrufen
3. In der Eltern Component auf das Event reagieren
Der Zugriff auf die Event-Daten erfolgt über die $event-
Variable
48. Pipes
Pipes werden verwendet, um Anzeigewerte zu formatieren.
Pipes sind Datenstreams mit Eingabe, Ausgabe und
Parametern. Mit den Parametern kann das Verhalten der
Pipe beeinflusst werden.
Eine Pipe wird durch das | vom Eingangswert getrennt. Die
Parameter werden durch Doppelpunkte getrennt.
Mehrere Pipes können miteinander verkettet werden. Sie
werden ebenfalls durch das |-Zeichen voneinander getrennt.
Die Ausgabe einer Pipe dient dann als Eingabe für die
nachfolgende.
51. RxJS
Die Reactive Extensions sind eine Bibliothek, die eine
Implementierung des Observer-Patterns darstellen. Damit
wird asynchrone Programmierung über definierte
Schnittstellen möglich. Reactive Extensions sind wesentlich
flexibler als Callback-Funktionen und Promises.
Reactive Extensions wurden von Microsoft entwickelt und
sind für viele Sprachen wie Java, C#, C++ oder JavaScript
verfügbar.
53. Operatoren
Operatoren sind die Werkzeugkiste von RxJS.
Operatoren erfüllen verschiedene Zwecke: Sie können
verwendet werden, um Observables zu erzeugen,
Datenströme zu Filtern oder Fehler in der Applikation zu
behandeln.
57. Services
Services kapseln Logik und können zur Datenverwaltung
verwendet werden. Mit Services kann außerdem mit anderen
Systemen kommuniziert werden.
Angular gibt für Services keine Struktur vor.
58. Services
ng generate service user
Dieser Befehl erzeugt einen Service. Dieser muss
anschließend noch als Provider registriert werden. Hierfür
gibt es zwei Möglichkeiten:
• Am Modul: Das gesamte Modul (app.module.ts unter dem
Schlüssel provider) teilt sich eine Instanz des Services
• An einer bestimmten Component: Eine Instanz pro
Component (Component-Datei im @Component-Decorator
als provider)
61. Dependency Injection
Bevor ein Service verwendet werden kann, muss er zunächst
geladen werden. Dies geschieht über die Dependency
Injection von Angular. Im einfachsten Fall werden die
Abhängigkeiten zu anderen Elementen der Applikation im
Konstruktor einer Klasse angegeben. Angular sucht
daraufhin nach einem passenden Provider und liefert
entweder eine bestehende Instanz aus dem Cache aus oder
erzeugt eine neue Instanz.
64. HTTP
Der http Service ist ein separates Modul des Angular-
Frameworks. Er dient der Kommunikation mit einem
Webserver und kann per Dependency Injection sowohl von
anderen Services als auch direkt in Components verwendet
werden.
1. HttpClientModule in app.module.ts als imports einbinden
(@angular/common/http)
2. HttpClient per Dependency Injection laden
3. Instanz verwenden.
65. HTTP
import { Injectable } from '@angular/core';
import { HttpClient } from ‘@angular/common/http';
import { User } from './user';
import 'rxjs/add/operator/map';
@Injectable()
export class UserService {
users: Array<User> = [];
constructor(private http: HttpClient) {}
load() {
this.http.get('/user')
.subscribe((users: Array<User>) => {
this.users = users;
});
}
}
66. HTTP
Die Methoden des HttpService geben keine Promise,
sondern ein RxJS-Observable zurück. Bei diesem Objekt
handelt es sich um einen asynchronen Datenstrom auf den
verschiedene Operatoren wie z.B. map angewendet werden
können. Mit der subscribe-Methode können Sie eine
Callback-Funktion registrieren.
67. HTTP
Wird bei der Entwicklung ng serve verwendet, kann die
proxy-Option von Webpack eingesetzt werden, um Anfragen
an den Server auf einen anderen Port umzuleiten. Damit kann
ein Backend an die, sich im Entwicklungsbetrieb
befindende, Applikation angeschlossen werden, ohne dass
Anpassungen am Quellcode erforderlich sind.
69. Reactive Store
Ein Reactive Store kapselt die Datenhaltung in einer internen
Datenstruktur. Der Store wird nicht direkt sondern über eine
definierte API modifiziert. Meist ist der Store mit dem HTTP-
Service verbunden.
Der Store selbst ist als Service umgesetzt, der in Components
per Dependency Injection eingebunden wird.
Die API verfügt meist über Methoden für CRUD-Operationen.
71. Reactive Store
Das Observable dient als readonly-Schnittstelle nach außen.
Intern werden Veränderungen über das BehaviorSubject
publiziert. Im internen dataStore werden die Daten
vorgehalten.
In das BehaviorSubject können sowohl Daten geschrieben
werden als auch gelesen werden. Außerdem kann es als
Observable exportiert werden.
72. Reactive Store
create(todo: Todo) {
this.dataStore.todos.push(todo);
this._todos.next(Object.assign({}, this.dataStore).todos);
}
Operationen auf dem Store laufen immer nach dem gleichen
Schema ab: Zuerst wird der lokale DataStore modifiziert,
danach die next-Methode des BehaviorSubjects mit einem
Klon des DataStores aufgerufen.
74. Forms
Angular verfügt über zwei Arten von Formularen: Template
Driven Forms und Reactive Forms.
Template Driven Forms sind einfacher und basieren
hauptsächlich auf Templates. Reactive Forms sind flexibler
und erfordern mehr TypeScript-Quellcode.
75. Template Driven Forms
1. Form Modul einbinden
2. Component erstellen
3. Template erzeugen
4. Formular-Elemente mit ngModel anbinden
5. name-Attribute einfügen
6. Validierung umsetzen
7. Formular abschicken
76. Template Driven Forms
Das Forms Modul stellt Direktiven wie ngModel und ngForm
zur Verfügung, die zur Erzeugung von Formularen erforderlich
sind.
77. Template Driven Forms
<label for="name">Name</label>
<input type="text" class="form-control" id="name" required
[(ngModel)]="model.name" name="name">
Das id-Attribut wird zur Verknüpfung mit dem label
verwendet. Das name-Attribut und die ngModel-Direktive
dienen der Formularbehandlung durch Angular. Das
required-Attribut ist eine Direktive, die einen Validator
aktiviert.
Durch ngModel werden die Werte direkt mit dem Model der
Component synchronisiert.
78. Template Driven Forms
State Wahr falsch
Element wurde
besucht
ng-touched ng-untouched
Wert hat sich geändert ng-dirty ng-pristine
Wert ist gültig ng-valid ng-invalid
Auf den Status eines Form-Elements kann zugegriffen
werden. Angular trackt verschiedene States als CSS-Klassen.
Damit können Styles angewendet werden.
79. Template Driven Forms
<div [hidden]="name.valid || name.pristine"
class=“error-message“>
Name is required
</div>
Über den Namen des Eingabefeldes kann auch auf dessen
Status zugegriffen werden und so z.B. Fehlermeldungen
ein- beziehungsweise ausgeblendet werden.
80. Template Driven Forms
Das Formular kann mit der ngSubmit-Direktive abgesendet
werden. Diese wird direkt an das Formular gebunden.
Sobald das Formular über den Submit-Button abgesendet
wird, wird die onSubmit-Methode der Component aufgerufen.
Durch das disabled-Attribut wird der Submit-Button erst
verfügbar, sobald das Formular valide ist.
<form (ngSubmit)="onSubmit()" #addressForm="ngForm">
…
<button type="submit" class="btn btn-success" [disabled]="!
addressForm.form.valid">Submit</button>
</form>
81. Reactive Forms
Damit Sie Reactive Forms benutzen können, müssen Sie in
Ihrem Root-Module das ReactiveFormsModule einbinden
(per Imports).
Danach erzeugen Sie in Ihrem Template die Struktur. Die
Verbindung zum Formular erfolgt hier über die
formControlName-Direktive.
Alles Weitere geschieht im Code der Component. Hier
verwenden Sie den FormBuilder, um das Formular zu
erzeugen.
85. Routing
Routing bezeichnet die Navigation innerhalb einer Single
Page Applikation über URLs. Eine Änderung der URL sorgt
dabei nicht für einen Page Load, sodass der State der
Applikation erhalten bleibt.
Angular verfügt über ein eigenes Modul zu diesem Zweck:
den Angular Router. Dieses Modul wird standardmäßig von
der Angular CLI mit installiert.
86. Routing
Damit der Router funktionieren, müssen Sie in Ihrer index.html-
Datei das Tag <base href=“/"> einfügen.
Danach konfigurieren Sie Ihre Routen und registrieren diese in
Ihrem Module.
88. Routing
Sie können in Ihrer Routing-Konfiguration auch eine
Standardroute für den Einstieg in Ihre Applikation definieren.
{ path: '',
redirectTo: '/list',
pathMatch: 'full'
}
89. Routing
Je nachdem welche Route Sie in Ihrer Applikation aktivieren,
wird ein anderer Component-Baum angezeigt. Mit dem
RouterOutlet geben Sie an, wo diese Components angezeigt
werden sollen.
<router-outlet></router-outlet>
90. Routing
Innerhalb Ihrer Applikation können Sie entweder über
RouterLinks oder aus dem Quellcode Ihrer Components
heraus navigieren.
<a routerLink="/list" routerLinkActive="active">Liste</a>
routerLinkActive hilft die aktive Route visuell zu kennzeichnen.
export class ListComponent {
constructor(private router: Router) { }
onShowDetails(address) {
this.router.navigate(['/address', address.id])
}
}
91. Routing
Routen können sowohl statische als auch dynamische Teile
beinhalten. Auf diese dynamischen Inhalte können Sie über
ein Observable (route.params) zugreifen.
export class AddressComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private service: AddressService
) {}
ngOnInit() {
this.route.params
.switchMap((params: Params) =>
this.service.get(parseInt(params['id']))
.subscribe((add: Address) => this.add = add);
}
}