navigation Navigation


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.

Beispiel: Observable
{{ myObservable$ | async }}
Beispiel: Promise
{{ myPromise | async }}
Beispiel: Promise mit 'as'
<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 man unsubscribe() in ngOnDestroy 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 der ChangeDetectionStrategy.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.

time.component.ts
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.

time.component.html
<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.

greeting.component.ts
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);
        });
    }

}
greeting.component.html
<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.

user.component.ts
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' }))
        );
    }

}
user.component.html
<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 trueaus, 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.

user.component.html
<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:

  1. (user$ | async) as user: Die AsyncPipe abonniert user$. Sobald ein Wert eintrifft, wird dieser der Template-Variablen user zugewiesen.
  2. *ngIf="... as user": Der *ngIf-Block wird angezeigt, wenn der zugewiesene Wert “truthy” ist.
  3. 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.
  4. else loadingUser: Optional kann ein else-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 mit as-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.
task.component.ts
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');
    }

}
task.component.html
<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>