Angular 20 の Resource API と httpResource を徹底解説:2026年の面接対策に必須の新機能
Angular 20 で導入された Resource API(resource・httpResource・rxResource)の仕組みと実装方法を、実践的なコード例とともに解説します。Zod によるスキーマバリデーション、ResourceStatus の文字列リテラル、HttpClient からの移行パターン、技術面接で問われる質問と回答まで網羅した総合ガイドです。

Angular 20 は、Signal ベースのデータフェッチにおいて Resource API と httpResource を中心的な役割に据えました。これらの API は、従来の HttpClient サブスクリプションパターンに伴う冗長なコードを、ローディング・エラー・データ取得完了の各状態を自動追跡するリアクティブプリミティブに置き換えるものです。
Resource API では、request が params に、loader が stream(rxResource の場合)にそれぞれ改名されています。ステータス値は数値 enum から文字列リテラル('idle'、'loading'、'resolved'、'error'、'reloading'、'local')に変更されました。httpResource は HttpClient を基盤としており、インターセプターや Zod バリデーションを追加設定なしで利用できます。
Angular 20 が提供する3種類の Resource バリアント
Angular 20 では、非同期データを Signal として読み込むための3つの手段が用意されています。それぞれ異なるユースケースを対象としていますが、共通のリアクティブモデルを採用しています。依存関係を宣言し、ローダーを定義し、結果を Signal を通じて消費するという流れです。
resource()は Promise と連携します。fetch()やその他の Promise ベースの API を使用する場合に適しています。rxResource()は Observable と連携します。debounceTime、retry、switchMapなどの RxJS オペレータが必要な場合に選択されます。httpResource()は Angular のHttpClientを直接ラップします。インターセプター、テストユーティリティ、スキーマバリデーションが追加の設定なしで機能します。
httpResource と他の2つの主要な違いは、内部で HttpClient を使用する点にあります。これにより、既存のインターセプターがそのまま適用されます。Angular 19 では resource() が HttpClient を完全に迂回していたため、インターセプターが機能しないという大きな課題がありましたが、httpResource によってこの問題は解消されています。
resource() によるユーザープロファイルの構築
resource() 関数は、params の計算値と loader 関数を受け取ります。params 内で読み取られた Signal の値が変化すると、loader が自動的に再実行されます。
import { Component, signal, resource } from '@angular/core';
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-profile',
template: `
@if (userResource.hasValue()) {
<h2>{{ userResource.value().name }}</h2>
<p>{{ userResource.value().email }}</p>
} @else if (userResource.isLoading()) {
<p>Loading profile...</p>
} @else if (userResource.error()) {
<p>Failed to load user</p>
}
`,
})
export class UserProfileComponent {
userId = signal(1);
// params produces the reactive dependency
// loader receives it and returns a Promise
userResource = resource<User, number>({
params: () => this.userId(),
loader: async ({ params: id, abortSignal }) => {
const res = await fetch(`/api/users/${id}`, { signal: abortSignal });
return res.json();
},
});
loadUser(id: number) {
this.userId.set(id); // triggers automatic refetch
}
}abortSignal パラメータにより、前回のリクエストが完了する前に userId が変更された場合、Angular は実行中のリクエストをキャンセルできます。手動でサブスクリプションを管理することなく、レースコンディションが防止されます。
httpResource によるリアクティブデータフェッチ
httpResource は、URL の宣言と HTTP 実行を単一の呼び出しに統合することで、ボイラープレートコードを排除します。戻り値の HttpResourceRef は、value、isLoading、error、status、headers を Signal として公開します。
import { Component, signal, computed } from '@angular/core';
import { httpResource } from '@angular/common/http';
interface Product {
id: number;
name: string;
price: number;
category: string;
}
@Component({
selector: 'app-product-list',
template: `
@if (products.hasValue()) {
@for (product of products.value(); track product.id) {
<div class="product-card">
<h3>{{ product.name }}</h3>
<span>{{ product.price | currency }}</span>
</div>
}
} @else if (products.isLoading()) {
<p>Loading products...</p>
}
`,
})
export class ProductListComponent {
category = signal('electronics');
// httpResource re-fetches whenever category() changes
products = httpResource<Product[]>(() => ({
url: '/api/products',
params: { category: this.category() },
}));
filterByCategory(cat: string) {
this.category.set(cat); // pending request is cancelled, new one starts
}
}ここで注目すべき点がいくつかあります。httpResource に渡される関数はリクエスト設定オブジェクトを返します。Angular はこの関数内の Signal の読み取りを追跡するため、category の変更は新しい GET リクエストをトリガーします。リクエストが既に実行中の場合、Angular は新しいリクエストを開始する前にそれをキャンセルします。
httpResource はデータの取得(GET リクエスト)専用に設計されています。POST、PUT、DELETE の操作に使用することは安全ではありません。リクエストのキャンセルにより、書き込み処理が途中で中断される可能性があるためです。書き込み操作には HttpClient を直接使用するか、サービスメソッドでミューテーションをラップする必要があります。
Zod と httpResource によるスキーマバリデーション
外部サービスからの API レスポンスは、想定されたデータ構造からずれることがあります。httpResource の parse オプションは、Zod のようなスキーマバリデーションライブラリと統合し、不正なデータを暗黙的に伝播させるのではなく、ランタイムで不一致を検出できるようにします。
import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';
import { z } from 'zod';
// Define the expected shape with Zod
const OrderSchema = z.object({
id: z.number(),
status: z.enum(['pending', 'shipped', 'delivered', 'cancelled']),
total: z.number().positive(),
items: z.array(z.object({
productId: z.number(),
quantity: z.number().int().positive(),
unitPrice: z.number().positive(),
})),
createdAt: z.string().datetime(),
});
type Order = z.infer<typeof OrderSchema>;
@Component({
selector: 'app-order',
template: `
@if (order.hasValue()) {
<h2>Order #{{ order.value().id }}</h2>
<p>Status: {{ order.value().status }}</p>
<p>Total: {{ order.value().total | currency }}</p>
} @else if (order.error()) {
<p>Invalid order data received</p>
}
`,
})
export class OrderComponent {
orderId = signal(42);
// parse validates the response before exposing it as a signal
order = httpResource<Order>(
() => `/api/orders/${this.orderId()}`,
{ parse: OrderSchema.parse }
);
}API が OrderSchema に適合しないデータを返した場合、resource は 'error' ステータスに遷移します。parse 関数の戻り値の型が value() の TypeScript 型も決定するため、スキーマ定義はランタイムバリデーターと型ジェネレーターの二重の役割を果たします。
Angular 20 における rxResource の stream と params
Angular 20 では、rxResource において loader が stream に、request が params にそれぞれ改名されました。この変更は、rxResource がサポートするストリーミングセマンティクスと命名を整合させるためです。stream 関数は Observable コンテキストを受け取り、Observable を返す必要があります。
import { Component, signal } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
import { debounceTime, switchMap } from 'rxjs';
interface SearchResult {
id: number;
title: string;
excerpt: string;
}
@Component({
selector: 'app-search',
template: `
<input (input)="query.set($any($event.target).value)" placeholder="Search..." />
@if (results.isLoading()) {
<p>Searching...</p>
}
@if (results.hasValue()) {
@for (item of results.value(); track item.id) {
<div>{{ item.title }}</div>
}
}
`,
})
export class SearchComponent {
private http = inject(HttpClient);
query = signal('');
// params (was "request") provides the reactive input
// stream (was "loader") returns an Observable
results = rxResource<SearchResult[], string>({
params: () => this.query(),
stream: ({ params: q }) =>
this.http.get<SearchResult[]>('/api/search', {
params: { q },
}),
});
}httpResource とは異なり、rxResource は Observable パイプラインに対する完全な制御を提供します。debounceTime や retry のようなオペレータを stream 内でチェーンできます。ただし、最も一般的なケース(単一の GET リクエスト)においては、httpResource の方が少ないコードで実装できます。
Angularの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
ステータス追跡:文字列リテラルが Enum を置き換える
Angular 20 では、ResourceStatus が数値 enum から文字列ユニオン型に変更されました。6つの値により、resource のライフサイクルをきめ細かく把握できます。
| ステータス | 意味 |
|---|---|
| 'idle' | params が undefined を返した。リクエストは未発行 |
| 'loading' | 初回リクエストが実行中 |
| 'reloading' | 前回の成功後に後続のリクエストが実行中 |
| 'resolved' | value() でデータが利用可能 |
| 'error' | リクエストが失敗。error() にエラーが格納 |
| 'local' | .set() または .update() でローカルに値が設定された状態 |
import { Component, signal, resource } from '@angular/core';
@Component({
selector: 'app-status-demo',
template: `
<p>Status: {{ data.status() }}</p>
@switch (data.status()) {
@case ('loading') { <spinner /> }
@case ('reloading') { <subtle-spinner /> }
@case ('resolved') { <data-table [rows]="data.value()" /> }
@case ('error') { <error-banner [error]="data.error()" /> }
@case ('idle') { <p>Select a filter to load data</p> }
}
`,
})
export class StatusDemoComponent {
filter = signal<string | undefined>(undefined);
data = resource({
params: () => this.filter(),
loader: async ({ params: f, abortSignal }) => {
const res = await fetch(`/api/data?filter=${f}`, { signal: abortSignal });
return res.json();
},
});
}params から undefined を返すと、ステータスは 'idle' に設定され、loader の実行が抑止されます。このパターンは、条件付きデータフェッチに適しています。ユーザーが入力を行うまでプロンプトを表示し、入力後にデータを読み込むといったフローを実現できます。
Angular 20 以降、'error' ステータスにある resource の value() を呼び出すとランタイム例外がスローされます。値の読み取りは必ず hasValue() でガードするか、status() を事前に確認する必要があります。Angular 19 では value() がエラー時に undefined を返していたため、この変更は破壊的変更に該当します。
Angular 20 httpResource の面接質問
Resource API は、Angular の技術面接において標準的なトピックとなりつつあります。以下は、API の表面的な知識を超えた、実践的な理解度を測るための質問です。
Q: httpResource が resource() では解決できない問題とは何ですか?
resource() は fetch() または任意の Promise ベースのローダーを使用し、Angular の HttpClient を迂回します。そのため、認証トークンの付与、ロギング、エラーハンドリングといったインターセプターが適用されません。httpResource は内部的に HttpClient を使用するため、インターセプター、テストユーティリティ(HttpTestingController)、withFetch() のような機能が追加設定なしで動作します。
Q: rxResource を httpResource より優先すべきケースはどのような場合ですか?
rxResource は stream 関数を通じて Observable の完全な制御を提供します。検索入力のデバウンス、指数バックオフによる失敗リクエストのリトライ、combineLatest による複数ストリームの結合など、RxJS オペレータが必要なデータパイプラインの場合に選択されます。標準的な GET リクエストであれば、httpResource の方が少ないコードで済みます。
Q: Signal が高速に変化した場合、Angular は並行リクエストをどのように処理しますか?
3つの resource バリアントすべてが、params が新しい値を生成した時点で保留中のリクエストをキャンセルします。httpResource と resource() では、AbortSignal が内部の fetch をキャンセルします。rxResource では、Angular が前の Observable のサブスクリプションを解除します。この仕組みにより、古いレスポンスが新しいデータを上書きするのを防ぎます。
Q: 'local' ステータスの目的は何ですか?
resource に対して .set() または .update() を呼び出すと、ローダーをトリガーせずにローカルで値が変更されます。ステータスは 'local' に遷移し、現在の値がサーバーから取得されたものではないことを示します。この仕組みは楽観的 UI 更新をサポートします。別のミューテーションリクエストが実行されている間、UI は即座に変更を反映できます。
Q: httpResource における Zod 統合の仕組みを説明してください。
parse オプションは (data: unknown) => T のシグネチャを持つ任意の関数を受け取ります。HTTP レスポンスが到着すると、httpResource はパースされた JSON を value() に設定する前に parse を通します。parse が例外をスロー(例:ZodError)した場合、resource は 'error' ステータスに遷移します。parse の戻り値の型が value() の TypeScript 型を決定するため、単一のスキーマ定義からランタイムの安全性とコンパイル時の型の両方が得られます。
Angular Signals と、それがフレームワーク全体とどのように統合されるかについてより深く掘り下げるには、Signals モジュールで computed signal、effect、リアクティビティモデルが解説されています。
HttpClient のサブスクリプションから httpResource への移行
既存の Angular アプリケーションでは、ngOnInit 内で HttpClient サブスクリプションを使用してデータを取得するか、Observable と AsyncPipe を組み合わせるのが一般的です。httpResource への移行は、以下のような予測可能なパターンに従います。
// BEFORE: manual subscription in ngOnInit
@Component({ /* ... */ })
export class BeforeComponent implements OnInit, OnDestroy {
private http = inject(HttpClient);
private destroy$ = new Subject<void>();
users: User[] = [];
loading = false;
error: string | null = null;
ngOnInit() {
this.loading = true;
this.http.get<User[]>('/api/users')
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (data) => { this.users = data; this.loading = false; },
error: (err) => { this.error = err.message; this.loading = false; },
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
// AFTER: httpResource handles lifecycle automatically
@Component({ /* ... */ })
export class AfterComponent {
users = httpResource<User[]>(() => '/api/users');
// No ngOnInit, no Subject, no manual unsubscribe
// Template uses users.value(), users.isLoading(), users.error()
}この移行により、ライフサイクル管理のボイラープレートが排除されます。コンポーネント破棄時のサブスクリプション解除は自動的に行われます。ローディング状態とエラー状態は resource に組み込まれており、個別の boolean フラグは不要です。
既にスタンドアロンコンポーネントを使用しているアプリケーションでは、移行は簡単です。HttpClient のインジェクションとサブスクリプションロジックを、単一の httpResource 宣言に置き換えるだけで完了します。
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
まとめ
httpResourceは、手動のHttpClientサブスクリプションを、ローディング・エラー・キャンセルを自動処理する単一のリアクティブ宣言に置き換えます- Promise ベースの API には
resource()、RxJS オペレータを用いた Observable パイプラインにはrxResource()、インターセプターサポート付きの標準的な HTTP 呼び出しにはhttpResource()を使用します - Angular 20 では
rxResourceのrequestがparamsに、loaderがstreamに改名されています。既存コードの更新が必要です - ステータス値は文字列リテラル(
'idle'、'loading'、'resolved'、'error'、'reloading'、'local')に変更され、Angular 19 の数値 enum に代わります httpResourceのparseオプションにより、Zod や Valibot を利用したランタイムスキーマバリデーションが可能になり、TypeScript 型の生成も兼ねます- error 状態の resource で
value()を呼び出すと Angular 20 では例外がスローされます。必ずhasValue()でガードするかstatus()を事前に確認してください - 3つの resource バリアントすべてが、依存関係の変更時に実行中のリクエストを自動キャンセルし、手動のキャンセルロジックなしでレースコンディションを防止します
タグ
共有
関連記事

Angular 19 面接質問集: Signals、SSR、必修概念
Angular 19 でよく出る面接質問: Signals、インクリメンタル ハイドレーション、zoneless change detection、新しいリアクティブ API。コード例と期待される回答付きで解説します。

Angular 2026のRxJS:演算子・Subject・Signals相互運用
Angular 2026のRxJS:本番で使われる演算子、Subject、Signalsとの相互運用パターンを、面接で最も問われるポイントとあわせて習得します。

Angular 19 Zoneless:Zone.jsなしのパフォーマンスと変更検知
Angular 19とAngular 20のZoneless変更検知により、Zone.jsを完全に削除し、バンドルサイズを33KB削減、レンダリング速度を30-40%向上させます。provideExperimentalZonelessChangeDetectionからprovideZonelessChangeDetectionへの移行パス、シグナルベースの反応性、既存アプリケーションの移行における落とし穴、SSR対応、パフォーマンスベンチマークを詳しく解説します。