Rezepte für Komponenten

In diesem Kapitel befinden sich verschiedene Rezepte, die hauptsächlich Komponenten betreffen. Manche der Rezepte können auch bei Direktiven angewendet werden. Sachen, wie z. B. die Kommunikation zwischen Komponenten, werden in diesem Kapitel behandelt.

Komponente und HTML-Template trennen

Problem

Ich hab ein langes Angular-Template und ich möchte das HTML getrennt von meiner Komponente halten.

Zutaten

Lösung

app.component.ts
1 ...
2 
3 @Component({
4   selector: 'app-root',
5   templateUrl: './app.component.html'
6 })
7 
8 ...

Erklärung:

  • Zeile 5: Statt der template-Eigenschaft, die wir in anderen Rezepten benutzt haben, nutzen wir jetzt die templateUrl-Eigenschaft. Der angegebene Pfad ist relativ zu der app.component.ts-Datei

Diskussion

Wichtig zu beachten ist, dass wir nur entweder die template-Eigenschaft oder die templateUrl-Eigenschaft verwenden können. Beide gleichzeitig gehen nicht. Da wir in diesem Buch angular-cli mit Webpack nutzen wird die Zeile 5 oben beim Kompilieren durch template: require('./app.component.html') ersetzt und Angular wird sich zur Laufzeit so verhalten als ob wir selbst das Template in der Komponente geschrieben haben. Falls andere Build-Tools benutzt werden, die die templateUrl-Eigenschaft nicht ersetzen, wird zur Laufzeit die Datei von Angular mittels XMLHttpRequest vom Server geholt und der Inhalt der Datei kompiliert und in das DOM gesetzt. Im allgemeinen ist es Toolabhängig, ob wir einen relativen Pfaden angeben können und wie dieser genau aussieht. Wer mehr über relative Pfade für Templates erfahren möchte, kann den Artikel Component-Relative Paths lesen.

templateUrl- vs. template-Eigenschaft

Beide Ansätze haben Vor- und Nachteile. Wir werden uns diese kurz anschauen.

templateUrl-Eigenschaft

Vorteile Nachteile
Übersichtlicher Extra Server-Aufruf
  (Gilt in unserem Fall nicht)
Logik und Markup sind Zwei offene Dateien
getrennt um eine Komponente zu implementieren
Code-Highlighting Wie genau der Pfad aussieht ist
  Toolabhängig
Auto-Vervollständigung  

template-Eigenschaft

Vorteile Nachteile
Die gesamte Komponente wird Unübersichtlich, wenn wir viel
in einer Datei definiert HTML haben
Kein extra Server-Aufruf Codehighlighting und
  Autovervollständigung sind
  editorabhängig
Keine Probleme mit Pfaden  

Ich persönlich versuche immer kleine Komponenten mit wenig HTML (10-15 Zeilen) zu schreiben und nutze dabei die template-Eigenschaft.

Code

Code auf Github

Live Demo auf angular2kochbuch.de

Das Template der Komponente vom CSS trennen

Problem

Ich möchte meine CSS-Styles getrennt von meinem Template und nicht in einem style-Tag im Template halten.

Zutaten

Lösung

Statt die CSS-Klassen im Template zu halten, können wir die styles-Eigenschaft der Komponente nutzen.

app.component.ts
 1 import { Component } from '@angular/core';
 2 
 3 @Component({
 4   selector: 'app-root',
 5   styles: [
 6     '.box {width: 100px; height: 100px; background-color: red; margin: 10px}',
 7     '.box-blue {background-color: blue;}'
 8   ],
 9   template: `
10     <div class="box"></div>
11     <div class="box"></div>
12     <div class="box box-blue"></div>
13     <div class="box"></div>
14   `
15 })
16 export class AppComponent {}

Erklärung:

  • Zeilen 5-8: CSS-Styles für unsere Komponente

Diskussion

Die styles-Eigenschaft einer Komponente erwartet ein Array von Strings. Ein String kann CSS-Styles für eine oder mehrere Klassen, Tags, etc. beinhalten. Jeder String wird dann zur Laufzeit als style-Tag in den DOM gesetzt. In unserem Beispiel werden zwei style-Tags im Head des Dokuments hinzugefügt.

Wenn wir in Komponenten CSS-Styles definieren, können die definierten CSS-Styles standardmäßig nur in der Komponente verwendet werden, in der diese definiert worden sind. Es ist dabei egal, ob wir die CSS-Styles als inline-styles mittels style-Tag, über die styles-Eigenschaft oder über die styleUrls-Eigenschaft der Komponente definieren. Dieses Verhalten kann uns vor Fehlern schützen und meidet Konflikte in den CSS-Styles, wenn wir z. B. Komponenten wiederverwenden. Die Kapselung von Styles und Komponenten wird in Angular “View-Encapsulation” genannt.

Code

Code auf Github

Live Demo auf angular2kochbuch.de

Weitere Ressourcen

Komponente und CSS trennen

Problem

Ich hab viele CSS-Klassen und ich möchte diese nicht in der Komponente halten, sondern in einer separaten CSS-Datei.

Zutaten

Lösung

In der ersten Lösung hatten wir den Pfad relativ zur index.html-Datei angegeben. Es gibt auch die Möglichkeit, den Pfad relativ zur app.component.ts-Datei zu definieren.

app.component.ts
 1 ...
 2 
 3 @Component({
 4   selector: 'app-root',
 5   styleUrls: ['./app.component.ts'],
 6   template: `
 7     <div class="box"></div>
 8     <div class="box"></div>
 9     <div class="box box-blue"></div>
10     <div class="box"></div>
11   `
12 })
13 
14 ...

Erklärung:

  • Zeile 5: Statt der styles-Eigenschaft, die wir in anderen Rezepten benutzt haben, nutzen wir jetzt die styleUrls-Eigenschaft. Der angegebene Pfad ist relativ zu der app.component.ts-Datei

Diskussion

Die styleUrls-Eigenschaft einer Komponente erwartet ein Array von Strings. Da wir in diesem Buch angular-cli mit Webpack nutzen wird die Zeile 5 oben beim Kompilieren durch styles: [require('./app.component.css')] ersetzt und Angular wird sich zur Laufzeit so verhalten als ob wir selbst die Styles in der Komponente geschrieben haben. Falls andere Build-Tools benutzt werden, die die styleUrls-Eigenschaft nicht ersetzen, wird zur Laufzeit die Datei von Angular mittels XMLHttpRequest vom Server geholt und der Inhalt der Datei wird als style-Tag in das DOM gesetzt. Im allgemeinen ist es Toolabhängig, ob wir einen relativen Pfaden angeben können und wie dieser genau aussieht. Wer mehr über relative Pfade für Styles erfahren möchte, kann den Artikel Component-Relative Paths lesen.

Wenn wir in Komponenten CSS-Styles definieren, können die definierten CSS-Styles standardmäßig nur in der Komponente verwendet werden, in der diese definiert worden sind. Es ist dabei egal, ob wir die CSS-Styles als inline-styles mittels style-Tag, über die styles-Eigenschaft der Komponente oder über die styleUrls-Eigenschaft der Komponente definieren. Dieses Verhalten kann uns vor Fehlern schützen und meidet Konflikte in den CSS-Styles, wenn wir z. B. Komponenten wiederverwenden. Die Kapselung von Styles und Komponenten wird in Angular “View-Encapsulation” genannt.

Die Diskussion styles- vs. stuleUrls-Eigenschaft ist analog zur template- vs. templateUrl-Eigenschaft Diskussion in Komponente und HTML-Template trennen.

Code

Code auf Github

Live Demo auf angular2kochbuch.de

Weitere Ressourcen

Daten an eine Unterkomponente mittels input-Eigenschaft übergeben

Problem

Ich möchte Daten, die sich in der Überkomponente befinden, an eine Unterkomponente übergeben.

Zutaten

Lösung

Wir werden uns als Erstes die Überkomponente (Parent) und als Zweites die Unterkomponente (Child) anschauen.

app.component.ts
 1 import { Component } from '@angular/core';
 2 
 3 @Component({
 4   selector: 'app-root',
 5   template: `
 6     <p>Parent Data: {{parentData}}</p>
 7     <app-second [childData]="parentData"></app-second>
 8   `
 9 })
10 export class AppComponent {
11   parentData: string = 'Hello World!';
12 }

Erklärung:

  • Zeile 7: Hier nutzen wir eine Eigenschaft-Bindung, um den Wert der parentData-Eigenschaft an die childData-Eigenschaft der Unterkomponente zu übergeben
second.component.ts
1 import { Component, Input } from '@angular/core';
2 
3 @Component({
4   selector: 'app-second',
5   template: '<p>Child Data: {{childData}}</p>'
6 })
7 export class SecondComponent {
8   @Input() childData: string;
9 }

Erklärung:

  • Zeile 8: Mit Hilfe des Input-Decorators (@Input) definieren wir die childData-Eigenschaft als input-Eigenschaft. Zu beachten ist, dass die input-Eigenschaft den gleichen Namen wie der Name zwischen den eckigen Klammern in der app.component.ts Zeile 8 haben muss

Diskussion

Änderungen in der parentData-Eigenschaft werden zur Laufzeit in die childData-Eigenschaft propagiert. Wir müssen also nichts tun, wenn sich z. B. durch Nutzer-Interaktion der Wert der parentData-Eigenschaft ändert. Wenn wir einen neuen Wert für die childData-Eigenschaft setzen, wird dieser Wert in der Parent-Component nicht sichtbar sein. Wir haben also hier eine Einweg-Datenbindung zwischen Parent- und Child-Component. Allerdings müssen wir aufpassen, wenn wir mit Objekten arbeiten. Falls die parentData-Eigenschaft ein Objekt ist und wir eine Eigenschaft dieses Objekts in der Child-Component ändern, ist die Änderung auch in der Parent-Component sichtbar. Der Grund dafür ist, dass Angular keine Kopie des Objekts erstellt, sondern die Referenz weitergibt. Wir haben für dieses Rezept zwei Beispielanwendungen auf Github. Der Code im Solution-Verzeichnis ist dieser, den wir hier gezeigt haben. Das Demo-Verzeichnis beinhaltet eine Anwendung, die demonstrieren soll, welche Datenänderungen wo sichtbar sind.

Code

Code auf Github für die Lösung

Code auf Github für die Demonstration

Live Demo (Code aus dem Demo-Verzeichnis) auf angular2kochbuch.de.

Daten an die Überkomponente mittels output-Eigenschaft übergeben

Problem

Ich möchte Daten, die sich in einer Unterkomponente befinden, an die Überkomponente übergeben.

Zutaten

Lösung

Wir werden uns als Erstes die Überkomponente (Parent) und als Zweites die Unterkomponente (Child) anschauen.

app.component.ts
 1 import { Component } from '@angular/core';
 2 
 3 @Component({
 4   selector: 'app-root',
 5   template: `
 6     <h1>Parent</h1>
 7     <p>Parent Data: {{parentData}}</p>
 8     <app-second (dataChange)="onDataChange($event)"></app-second>
 9   `
10 })
11 export class AppComponent {
12   parentData: string = 'Initial Data';
13 
14   onDataChange(data) {
15     this.parentData = data;
16   }
17 }

Erklärung:

  • Zeile 8: Die Syntax mit den Klammern für eine Event-Bindung kennen wir schon. Nur nutzen wir hier keinen Browser-Event, sondern einen Event, den wir in der Child-Component definiert haben (siehe second.component.ts Zeile 12). Wenn das Event ausgelöst wird, rufen wir die onDataChange-Methode auf und übergeben das Event-Objekt
  • Zeilen 14-16: Methode, die aufgerufen wird, wenn das dataChange-Event ausgelöst wird
second.component.ts
 1 import {
 2     Component,
 3     Output,
 4     EventEmitter
 5 } from '@angular/core';
 6 
 7 @Component({
 8   selector: 'app-second',
 9   template: `
10     <h1>Child</h1>
11     <button (click)="sendData()">Send data to Parent</button>
12   `
13 })
14 export class SecondComponent {
15   @Output() dataChange = new EventEmitter();
16 
17   sendData() {
18     this.dataChange.emit('Child Data');
19   }
20 }

Erklärung:

  • Zeile 11: Definition einer output-Eigenschaft namens “dataChange”. Die output-Eigenschaft besitzt als Wert eine Instanz der EventEmitter-Klasse
  • Zeilen 17-19: Methode, die aufgerufen wird, wenn der Nutzer auf den Button klickt
    • Zeile 16: Die emit-Methode triggert das dataChange-Event. Der Parameter der Methode ist das Event-Objekt, das übergeben wird (siehe auch app.component.ts Zeilen 9 und 16)

Diskussion

Wir können die EventEmitter-Klasse nutzen, um eigene Events zu definieren. Diese Events können mittels der emit-Methode getriggert werden, um deren Listener zu informieren, dass das Event ausgelöst worden ist. Das machen wir uns zunutze und definieren immer unsere Output-Eigenschaften als Events, auf die eine Parent-Component hören kann, indem diese eine Event-Bindung nutzt.

Code

Code auf Github

Live Demo auf angular2kochbuch.de

Weitere Ressourcen

Code ausführen bei der Initialisierung einer Komponente

Problem

Ich möchte bei der Initialisierung meiner Komponente Code ausführen z. B. um Daten vom Server zu holen.

Zutaten

Lösung

app.component.ts
 1 import { Component, OnInit } from '@angular/core';
 2 
 3 @Component({
 4   selector: 'app-root',
 5   template: '<div>Hello World!</div>'
 6 })
 7 export class AppComponent implements OnInit {
 8   ngOnInit() {
 9     console.log('Initialization');
10   }
11 }

Erklärung:

  • Zeile 7: Hier nutzen wir das OnInit-Interface und sagen, dass unsere Komponente dieses Interface implementiert
  • Zeilen 8-10: Die ngOnInit-Methode wird bei der Initialisierung der Komponente von Angular aufgerufen. Sie ist Teil des OnInit-Interfaces

Diskussion

Angular bietet uns sogenannte “Lifecycle Hooks” an. Diese sind Methoden, die unsere Komponente implementieren kann und werden automatisch zu bestimmten Zeitpunkten von Angular aufgerufen. Der OnInit-Hook, den wir hier nutzen wird bei der Initialisierung der Komponente, nach der Konstruktorfunktion aufgerufen. Der implements OnInit Teil ist eigentlich optional, es wird aber empfohlen diesen zu nutzen, weil dann der Compiler eine Fehlermeldung ausgeben kann, falls wir den Namen der Methode falsch schreiben.

OnInit vs. Konstruktorfunktion

Auf den ersten Blick könnte man meinen, dass die zwei äquivalent sind bzw. dass wir Initialisierungscode genau so gut in den Konstruktor schreiben können. Das stimmt nur bedingt.

Die Nutzung von ngOnInit hat gewisse Vorteile. Als Erstes ist es einfacher Unit-Tests dafür zu schreiben, weil wir da besser den Zeitpunkt des Aufrufs kontrollieren können. Die Konstruktorfunktion wird bei Unit-Tests meistens von Angular automatisch aufgerufen. Die ngOnInit-Methode hingegen nicht.

Ein weiterer Vorteil von ngOnInit ist, dass diese Methode erst dann aufgerufen wird nach dem die Eigenschaft-Bindungen (@Input) der Komponente ihre Werte erhalten haben. In der Konstruktorfunktion sind die Eigenschaft-Bindungen noch undefined. Wenn wir also Zugriff auf Werte von Eigenschaft-Bindungen brauchen, müssen wir ngOnInit nutzen.

Im Allgemeinen wird empfohlen den OnInit-Hook für Initialisierungscode und die Konstruktorfunktion für Dependency Injection zu nutzen.

Code

Code auf Github

Live Demo auf angular2kochbuch.de

Weitere Ressourcen

  • Mehr Informationen über Lifecycle Hooks gibt es auf der Angular 2 Webseite

Code ausführen bei der Zerstörung (destroy) einer Komponente

Problem

Ich möchte informiert werden bevor eine Komponente von Angular zerstört wird, so dass ich z. B. die unsubscribe-Methode von einem Observable aufrufen kann.

Zutaten

Lösung

app.component.ts
 1 import { Component, OnDestroy } from '@angular/core';
 2 
 3 @Component({
 4   selector: 'app-second',
 5   template: '<div>My Name is ...</div>'
 6 })
 7 export class SecondComponent implements OnDestroy {
 8   ngOnDestroy() {
 9     console.log('Destroy');
10   }
11 }

Erklärung:

  • Zeile 7: Hier nutzen wir das OnDestroy-Interface und sagen, dass unsere Komponente dieses Interface implementiert
  • Zeilen 8-10: Die ngOnDestroy-Methode wird bei der Zerstörung der Komponente von Angular aufgerufen. Sie ist Teil des OnDestroy-Interfaces

Diskussion

Angular bietet uns sogenannte “Lifecycle Hooks” an. Diese sind Methoden, die unsere Komponente implementieren kann und werden automatisch zu bestimmten Zeitpunkten von Angular aufgerufen. Der OnDestroy-Hook, den wir hier nutzen wird z. B. aufgerufen, wenn die Komponente aus dem DOM entfernt wird. Der implements OnDestroy Teil ist eigentlich optional, es wird aber empfohlen diesen zu nutzen, weil dann der Compiler eine Fehlermeldung ausgeben kann, falls wir den Namen der Methode falsch schreiben.

Die ngOnDestroy-Methode ist der ideale Ort, um Cleanup-Code zu schreiben. Z. B. können wir hier eigene Callback-Funktionen entfernen, um Memory-Leaks zu vermeiden. Hier haben wir nur den Code für die SecondComponent gezeigt. Im Beispiel-Code auf Github wird auch die app.component.ts-Datei angepasst, so dass diese die SecondComponent aus dem DOM entfernen kann, damit die ngOnDestroy-Methode auch aufgerufen wird.

Code

Code auf Github

Live Demo auf angular2kochbuch.de

Weitere Ressourcen

  • Mehr Informationen über Lifecycle Hooks gibt es auf der Angular 2 Webseite