# Angular 20 の Resource API と httpResource を徹底解説:2026年の面接対策に必須の新機能
> Angular 20 で導入された Resource API(resource・httpResource・rxResource)の仕組みと実装方法を、実践的なコード例とともに解説します。Zod によるスキーマバリデーション、ResourceStatus の文字列リテラル、HttpClient からの移行パターン、技術面接で問われる質問と回答まで網羅した総合ガイドです。
- Published: 2026-05-30
- Updated: 2026-05-30
- Author: SharpSkill
- Tags: angular, angular-20, resource-api, httpResource, signals
- Reading time: 12 min
---
Angular 20 は、Signal ベースのデータフェッチにおいて [Resource API](https://angular.dev/guide/signals/resource) と `httpResource` を中心的な役割に据えました。これらの API は、従来の `HttpClient` サブスクリプションパターンに伴う冗長なコードを、ローディング・エラー・データ取得完了の各状態を自動追跡するリアクティブプリミティブに置き換えるものです。
> **Angular 20 での変更点**
>
> 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` を使用する点にあります。これにより、既存の[インターセプター](https://angular.dev/guide/http/interceptors)がそのまま適用されます。Angular 19 では `resource()` が `HttpClient` を完全に迂回していたため、インターセプターが機能しないという大きな課題がありましたが、`httpResource` によってこの問題は解消されています。
## resource() によるユーザープロファイルの構築
`resource()` 関数は、`params` の計算値と `loader` 関数を受け取ります。`params` 内で読み取られた Signal の値が変化すると、`loader` が自動的に再実行されます。
```typescript
// user-profile.component.ts
import { Component, signal, resource } from '@angular/core';
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-profile',
template: `
@if (userResource.hasValue()) {
{{ userResource.value().name }}
{{ userResource.value().email }}
} @else if (userResource.isLoading()) {
Loading profile...
} @else if (userResource.error()) {
Failed to load user
}
`,
})
export class UserProfileComponent {
userId = signal(1);
// params produces the reactive dependency
// loader receives it and returns a Promise
userResource = resource({
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 として公開します。
```typescript
// product-list.component.ts
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) {
{{ product.name }}
{{ product.price | currency }}
}
} @else if (products.isLoading()) {
Loading products...
}
`,
})
export class ProductListComponent {
category = signal('electronics');
// httpResource re-fetches whenever category() changes
products = httpResource(() => ({
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 は読み取り専用です**
>
> `httpResource` はデータの取得(GET リクエスト)専用に設計されています。POST、PUT、DELETE の操作に使用することは安全ではありません。リクエストのキャンセルにより、書き込み処理が途中で中断される可能性があるためです。書き込み操作には `HttpClient` を直接使用するか、サービスメソッドでミューテーションをラップする必要があります。
## Zod と httpResource によるスキーマバリデーション
外部サービスからの API レスポンスは、想定されたデータ構造からずれることがあります。`httpResource` の `parse` オプションは、[Zod](https://zod.dev/) のようなスキーマバリデーションライブラリと統合し、不正なデータを暗黙的に伝播させるのではなく、ランタイムで不一致を検出できるようにします。
```typescript
// order.component.ts
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;
@Component({
selector: 'app-order',
template: `
@if (order.hasValue()) {
Order #{{ order.value().id }}
Status: {{ order.value().status }}
Total: {{ order.value().total | currency }}
} @else if (order.error()) {
Invalid order data received
}
`,
})
export class OrderComponent {
orderId = signal(42);
// parse validates the response before exposing it as a signal
order = httpResource(
() => `/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 を返す必要があります。
```typescript
// search.component.ts
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: `
@if (results.isLoading()) {
Searching...
}
@if (results.hasValue()) {
@for (item of results.value(); track item.id) {
{{ item.title }}
}
}
`,
})
export class SearchComponent {
private http = inject(HttpClient);
query = signal('');
// params (was "request") provides the reactive input
// stream (was "loader") returns an Observable
results = rxResource({
params: () => this.query(),
stream: ({ params: q }) =>
this.http.get('/api/search', {
params: { q },
}),
});
}
```
`httpResource` とは異なり、`rxResource` は Observable パイプラインに対する完全な制御を提供します。`debounceTime` や `retry` のようなオペレータを `stream` 内でチェーンできます。ただし、最も一般的なケース(単一の GET リクエスト)においては、`httpResource` の方が少ないコードで実装できます。
## ステータス追跡:文字列リテラルが Enum を置き換える
Angular 20 では、`ResourceStatus` が数値 enum から文字列ユニオン型に変更されました。6つの値により、resource のライフサイクルをきめ細かく把握できます。
| ステータス | 意味 |
|---|---|
| `'idle'` | `params` が `undefined` を返した。リクエストは未発行 |
| `'loading'` | 初回リクエストが実行中 |
| `'reloading'` | 前回の成功後に後続のリクエストが実行中 |
| `'resolved'` | `value()` でデータが利用可能 |
| `'error'` | リクエストが失敗。`error()` にエラーが格納 |
| `'local'` | `.set()` または `.update()` でローカルに値が設定された状態 |
```typescript
// status-demo.component.ts
import { Component, signal, resource } from '@angular/core';
@Component({
selector: 'app-status-demo',
template: `
Status: {{ data.status() }}
@switch (data.status()) {
@case ('loading') { }
@case ('reloading') { }
@case ('resolved') { }
@case ('error') { }
@case ('idle') { Select a filter to load data
}
}
`,
})
export class StatusDemoComponent {
filter = signal(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` の実行が抑止されます。このパターンは、条件付きデータフェッチに適しています。ユーザーが入力を行うまでプロンプトを表示し、入力後にデータを読み込むといったフローを実現できます。
> **error 状態での value() アクセスについて**
>
> Angular 20 以降、`'error'` ステータスにある resource の `value()` を呼び出すとランタイム例外がスローされます。値の読み取りは必ず `hasValue()` でガードするか、`status()` を事前に確認する必要があります。Angular 19 では `value()` がエラー時に `undefined` を返していたため、この変更は破壊的変更に該当します。
## Angular 20 httpResource の面接質問
Resource API は、[Angular の技術面接](/blog/angular/angular-19-interview-questions-signals-ssr-must-know)において標準的なトピックとなりつつあります。以下は、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](/technologies/angular/interview-questions/angular-signals) と、それがフレームワーク全体とどのように統合されるかについてより深く掘り下げるには、Signals モジュールで computed signal、effect、リアクティビティモデルが解説されています。
## HttpClient のサブスクリプションから httpResource への移行
既存の Angular アプリケーションでは、`ngOnInit` 内で `HttpClient` サブスクリプションを使用してデータを取得するか、Observable と `AsyncPipe` を組み合わせるのが一般的です。`httpResource` への移行は、以下のような予測可能なパターンに従います。
```typescript
// BEFORE: manual subscription in ngOnInit
@Component({ /* ... */ })
export class BeforeComponent implements OnInit, OnDestroy {
private http = inject(HttpClient);
private destroy$ = new Subject();
users: User[] = [];
loading = false;
error: string | null = null;
ngOnInit() {
this.loading = true;
this.http.get('/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(() => '/api/users');
// No ngOnInit, no Subject, no manual unsubscribe
// Template uses users.value(), users.isLoading(), users.error()
}
```
この移行により、ライフサイクル管理のボイラープレートが排除されます。コンポーネント破棄時のサブスクリプション解除は自動的に行われます。ローディング状態とエラー状態は resource に組み込まれており、個別の boolean フラグは不要です。
既に[スタンドアロンコンポーネント](/blog/angular/angular-standalone-components-migration-best-practices)を使用しているアプリケーションでは、移行は簡単です。`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 バリアントすべてが、依存関係の変更時に実行中のリクエストを自動キャンセルし、手動のキャンセルロジックなしでレースコンディションを防止します
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/ja/blog/angular/angular-20-resource-api-httpresource-tutorial