AsyncPipe
Die AsyncPipe in Angular ist eine mächtige, integrierte Pipe, bereitgestellt im CommonModule.
Was ist AsyncPipe
Die AsyncPipe ist eine sogenannte impure Pipe in Angular. Sie abonniert (subscribe
) ein Observable oder Promise und gibt den zuletzt ausgegebenen Wert zurück. Wenn neue Werte ausgegeben werden, markiert die AsyncPipe die Komponente zur Überprüfung durch die Change Detection, sodass die Ansicht aktualisiert wird.
Besonders wichtig: Wenn die Komponente zerstört wird, beendet (unsubscribe
) die AsyncPipe automatisch die Subscription.
Sie gehört zu @angular/common Modul, welches standardmäßig im BrowserModule (für Webanwendungen) oder CommonModule (für Feature-Module) importiert wird.
Syntax und Verwendung
Die Syntax ist relativ einfach. Man wendet die Pipe im Template mit dem Pipe-Operator (|
) auf ein Observable oder Promise an.
{{ myObservable$ | async }}
{{ myPromise | async }}
<div *ngIf="promise | async as value">{{ value }}</p>
Hinweis: Das Suffix $
bei myObservable$
ist eine gängige Konvention, um Variablen zu kennzeichnen, die ein Observable halten. Technisch ist dies nicht notwendig.
Warum wird AsyncPipe verwendet?
Der Einsatz von AsyncPipe bietet mehrere signifikante Vorteile gegenüber der manuellen Verwaltung von Subscriptions.
- Automatische Subscription/Unsubscription: Man muss sich nicht darum kümmern,
subscribe()
aufzurufen oder sicherzustellen, dass manunsubscribe()
inngOnDestroy
aufruft. Die AsyncPipe übernimmt das Lebenszyklusmanagement der Subscription automatisch. Das reduziert Boilerplate-Code und Memory-Leaks. - Saubere Komponenten-Logik: Die Komponenten bleiben schlanker, da die Logik zur Verwaltung von Subscriptions und das Zuweisen von Werten an lokale Variablen entfällt. Der Fokus liegt auf der Bereitstellung der Datenströme (Observables/Promises).
- Verbesserte Performance mit
OnPush
: Die AsyncPipe arbeitet hervorragend mit derChangeDetectionStrategy.OnPush
zusammen. Da sie die Komponente nur dann zur Überprüfung markiert, wenn ein neuer Wert eintrifft, optimiert sie die Performance, indem unnötige Change Detection Zyklen vermieden werden. - Deklarativer Ansatz: Man beschreibt im Template, welche Daten angezeigt werden sollen, nicht wie sie abonniert und aktualisiert werden.
Funktionsweise von AsyncPipe
Im Kern führt AsyncPipe folgende Schritte aus.
1. Eingabe
Sie erhält ein Observable
oder Promise
als Eingabe.
2. Subscription
Sie abonniert das Observable
oder registriert einen .then()
Callback beim Promise
.
3. Wertspeicherung
Sie speichert intern den zuletzt empfangenen Wert.
4. Ausgabe
Sie gibt den gespeicherten Wert zur Anzeige im Template zurück. Initial ist dieser Wert null
, bis der erste Wert eintrifft.
5. Change Detection
Bei jedem neuen Wert vom Observable
oder bei der Auflösung des Promise
ruft die AsyncPipe intern markForCheck()
auf. Dies signalisiert Angular, dass die Komponente und ihre Kinder bei der nächsten Change Detection überprüft werden müssen.
6. Unsubscription
Wenn die Komponente, in der die Pipe verwendet wird, zerstört wird (z.B. durch Navigation oder *ngIf=false
), beendet die AsyncPipe automatisch die Subscription bzw. entfernt den Callback.
Unterstützte Datentypen
Die AsyncPipe kann direkt mit zwei Arten von asynchronen Datenquellen umgehen.
Observables (Observable<T>
)
Dies ist der häufigste Anwendungsfall, insbesondere bei Verwendung von RxJS für die Zustandsverwaltung oder HTTP-Anfragen (HttpClient
). Die Pipe abonniert das Observable und zeigt jeden neu ausgegebenen Wert an.
Promises (Promise<T>
)
Wenn ein Promise an die AsyncPipe übergeben wird, wartet sie auf dessen Auflösung (resolve
) und zeigt dann den aufgelösten Wert an. Sie zeigt nur den finalen Wert an, nicht eventuelle Zwischenzustände.
Beispiel - Anzeigen eines einfachen Observable-Wertes
Man stelle sich vor, man hat einen Service, der die aktuelle Uhrzeit als Observable bereitstellt.
import { Component, OnInit } from '@angular/core';
import { AsyncPipe, DatePipe } from '@angular/common';
import { Observable, interval, map, startWith } from 'rxjs';
@Component({
selector: 'app-time',
standalone: true,
imports: [
AsyncPipe,
DatePipe
]
templateUrl: './time.component.html'
})
export class TimeComponent implements OnInit {
currentTime$: Observable<Date>;
ngOnInit(): void {
this.currentTime$ = interval(1000).pipe(
map(() => new Date()),
startWith(new Date())
);
}
}
Wichtig: AsyncPipe und DatePipe müssen auf @angular/common
importiert und im Component unter imports
platziert werden.
<h2>Current time</h2>
<p>{{ currentTime$ | async | date:'mediumTime' }}
Die AsyncPipe abonniert currentTime$
. Jede Sekunde gibt das interval
einen Wert aus. map
wandelt ihn in die aktuelle Zeit um und die AsyncPipe sorgt dafür, dass die neue Zeit im Template angezeigt wird.
Es ist nicht notwendig subscribe()
sowie unsubscribe()
zu verwenden.
Beispiel - AsyncPipe mit Promise
Angenommen, eine Funktion gibt ein Promise zurück, das nach einer Verzögerung einen Text liefert.
import { Component, OnInit } from '@angular/core';
import { AsyncPipe } from '@angular/common';
@Component({
selector: 'app-greeting',
standalone: true,
imports: [
AsyncPipe
]
templateUrl: './greeting.component.html'
})
export class GreetingComponent implements OnInit {
greetingPromise: Promise<string>;
ngOnInit(): void {
this.greetingPromise = new Promise((resolve) => {
setTimeout(() => {
resolve("Hello from promise");
}, 2000);
});
}
}
<h2>Greeting</h2>
<p>{{ greetingPromise | async }}
Beispiel - Verwendung mit ngIf
Manchmal möchte man nur einen Teil des Templates anzeigen, wenn Daten verfügbar sind.
import { Component, OnInit } from '@angular/core';
import { CommonModule, AsyncPipe } from '@angular/common';
import { Observable, of, timer, map } from 'rxjs';
interface User {
id: number;
name: string;
}
@Component({
selector: 'user-app',
imports: [
CommonModule,
AsyncPipe
],
templateUrl: './user.component.html'
})
export class UserComponent implements OnInit {
user$!: Observable<User | null>;
ngOnInit() {
this.user$ = timer(1500).pipe(
map(() => ({ id: 1, name: 'Alice' }))
);
}
}
<h2>User information</h2>
<div *ngIf="(user$ | async)">
<p>User loaded</p>
<p>Name: {{ (user$ | async)?.name }}</p>
</div>
<div *ngIf="!(user$ | async)">
<p>Loading data ...</p>
</div>
*ngIf="(user$ | async)"
wertet zu true
aus, sobald das user$
Observable einen “truthy” Wert (in diesem Fall das Objekt) ausgibt. Der Lade-Indikator wird angezeigt, solange der Wert null
oder undefined
ist.
Hinweis: Wenn man innerhalb des *ngIf
-Blocks erneut auf die Daten zugreifen muss ({{ (user$ | async)?.name }}
) wird die AsyncPipe erneut angewendet. Dies führt zu einer zweiten, unabhängigen Subscription auf dasselbe Observable. Das ist ineffizient und kann bei Observables mit Seiteneffekten (wie HTTP-Requests) zu unerwünschtem Verhalten führen.
Beispiel - Verwendung mit ngIf
und as
Um das, im oberen Beispiel beschriebene, Problem der mehrfachen Subscriptions zu lösen, bietet Angular die as
Syntax in Kombination mit *ngIf
.
<h2>User information</h2>
<div *ngIf="(user$ | async) as user; else loadingUser">
<p>User loaded</p>
<p>ID: {{ user.id }}</p>
<p>Name: {{ user.name }}</p>
</div>
<ng-template #loadingUser>
<p>Loading data ...</p>
</ng-template>
Folgendes passiert hier:
(user$ | async) as user
: Die AsyncPipe abonniertuser$
. Sobald ein Wert eintrifft, wird dieser der Template-Variablenuser
zugewiesen.*ngIf="... as user"
: Der*ngIf
-Block wird angezeigt, wenn der zugewiesene Wert “truthy” ist.- Innerhalb des Blocks kann man die Variable
user
beliebig oft verwenden, um auf die Eigenschaften des aufgelösten Objekts zuzugreifen (user.id
,user.name
), ohne zusätzliche Subscriptions zu erzeugen. else loadingUser
: Optional kann einelse
-Block mit einer Template-Referenz (#loadingUser
) angegeben werden, der angezeigt wird, solange die Bedingung von*ngIf
false
ist.
Beispiel - HTTP-Request und AsyncPipe
In diesem Beispiel wird folgendes gemacht.
- Es wird ein Observable
$task
deklariert, das vom HTTP-Service zurückgegeben wird. - Die AsyncPipe abonniert das Observable und zeigt die Daten an, sobald sie verfügbar sind.
- Es wird
*ngIf
mitas
-Syntax verwendet, um den Wert an eine lokale Variable zu binden (der Vorteil davon ist in diesem Artikel am anderen Beispiel verdeutlicht). - Es wird eine Ladeanzeige angezeigt, solange das Observable keinen Wert emittiert hat.
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
interface ITask {
id: number;
userId: number;
title: string;
completed: boolean;
}
@Component({
selector: 'app-task',
imports: [
CommonModule
],
templateUrl: './task.component.html',
styleUrl: './task.component.scss'
})
export class TaskComponent {
task$!: Observable<ITask>;
constructor(private http: HttpClient) {
this.task$ = this.http.get<ITask>('https://jsonplaceholder.typicode.com/todos/1');
}
}
<div *ngIf="task$ | async as task; else loading">
<h2>Task: {{ task.title }}</h2>
<p>User ID: {{ task.userId }}</p>
<p>Completed: {{ task.completed ? 'Yes' : 'No' }}</p>
</div>
<ng-template #loading>
<p>Load task ...</p>
</ng-template>