← 解説「asyncパイプ — 購読と解除をAngularに任せる」に戻る
サンプルコードで身につける: asyncパイプ — 購読と解除をAngularに任せる
解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。
1最小のasyncパイプ — 値をそのまま表示する
APIから取得した1件のデータを、subscribeを書かずにasyncパイプで直接表示する最小例です。コンポーネント側にはObservableのプロパティが1つあるだけで、状態への詰め替えコードがありません。購読も解除もAngularが自動で行います。
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { AsyncPipe } from '@angular/common';
type User = { id: number; name: string };
@Component({
selector: 'app-profile',
imports: [AsyncPipe],
// async パイプが subscribe と解除を代行する
template: '<p>ようこそ {{ (user$ | async)?.name }} さん</p>',
})
export class ProfileComponent {
private http = inject(HttpClient);
// $ 付きの名前は「Observable である」ことを示す慣習
user$ = this.http.get<User>('/api/me');
}
2@if + as構文で「届いてから表示」する
@if (user$ | async; as user) と書くと、値が届くまでは@elseブロックが表示され、届いたら user という名前でブロック内から使えます。null チェックと読み込み中表示を1つの構文でまかなえる、asyncパイプの定番パターンです。
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { AsyncPipe } from '@angular/common';
type User = { id: number; name: string; email: string };
@Component({
selector: 'app-account',
imports: [AsyncPipe],
// 届いた値に user という名前を付けてブロック内で使い回す
template:
'@if (user$ | async; as user) {' +
' <h2>{{ user.name }}</h2><p>{{ user.email }}</p>' +
'} @else { <p>読み込み中...</p> }',
})
export class AccountComponent {
private http = inject(HttpClient);
user$ = this.http.get<User>('/api/me');
}
3@forと組み合わせて一覧を表示する
一覧データのObservableをasyncパイプで受け、@forでそのまま並べる例です。手動subscribeの版と比べると、signalへのsetもngOnInitも不要になっていることがわかります。一覧表示だけの画面なら、この形が最短の書き方です。
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { AsyncPipe } from '@angular/common';
type Article = { id: number; title: string };
@Component({
selector: 'app-article-list',
imports: [AsyncPipe],
// as構文で1回だけ購読し、@for で並べる
template:
'@if (articles$ | async; as articles) {' +
' @for (a of articles; track a.id) { <p>{{ a.title }}</p> }' +
' @empty { <p>記事がありません</p> }' +
'}',
})
export class ArticleListComponent {
private http = inject(HttpClient);
articles$ = this.http.get<Article[]>('/api/articles');
}
4手動subscribeとの比較 — 解除忘れが起きない
同じ画面を手動subscribeで書いた場合との比較です。手動版はngOnDestroyでの解除を忘れるとコンポーネント破棄後も購読が生き続けますが、asyncパイプ版にはそもそも解除するコードが存在しません。「書かなくていいコードはミスも起きない」ことを示す例です。
import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Subscription, interval } from 'rxjs';
// ── 手動 subscribe 版: 解除の管理が自分の責任になる ──
@Component({
selector: 'app-clock-manual',
template: '<p>経過秒数: {{ count() }}</p>',
})
export class ClockManualComponent implements OnInit, OnDestroy {
count = signal(0);
private sub?: Subscription;
ngOnInit() {
this.sub = interval(1000).subscribe((n) => this.count.set(n));
}
ngOnDestroy() {
// これを忘れるとコンポーネント破棄後も購読が動き続ける
this.sub?.unsubscribe();
}
}
// ── async パイプ版: 解除のコード自体が存在しない ──
// @Component({
// imports: [AsyncPipe],
// template: '<p>経過秒数: {{ count$ | async }}</p>',
// })
// export class ClockComponent {
// count$ = interval(1000);
// }
5pipeで加工したストリームをasyncパイプで受ける
コンポーネントではObservableをpipeで加工する定義だけを書き、表示はasyncパイプに任せる分業の例です。「加工はクラス側、購読はテンプレート側」という役割分担にすると、テストしやすく見通しの良い構造になります。
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { AsyncPipe } from '@angular/common';
import { map } from 'rxjs';
type Task = { id: number; title: string; done: boolean };
@Component({
selector: 'app-todo-summary',
imports: [AsyncPipe],
template: '<p>未完了タスク: {{ remaining$ | async }} 件</p>',
})
export class TodoSummaryComponent {
private http = inject(HttpClient);
// 加工(未完了の件数に変換)はクラス側、購読はテンプレート側
remaining$ = this.http
.get<Task[]>('/api/tasks')
.pipe(map((tasks) => tasks.filter((t) => !t.done).length));
}