Angular Resource API と httpResource 完全ガイド(Angular 22 で正式版)

Angular の Resource API と httpResource は Angular 22 で正式版となりました。resource()、rxResource()、httpResource()、依存リソースのための chain() ヘルパー、Zod バリデーション、面接で問われる質問を解説します。

Angular 20 Resource API and httpResource tutorial

Angular の Resource API と httpResource は、ローディング・エラー・データ取得完了の各状態を自動追跡するシグナルベースのデータフェッチを提供します。Angular 20 で導入されたこれらの API は、Angular 22 で正式版となり、従来の HttpClient サブスクリプションパターンに伴う冗長なコードをリアクティブプリミティブに置き換えました。

Angular 22 で正式版に

Resource API(resource()、rxResource()、httpResource())は Angular 22(2026年6月)で実験的機能から正式版に昇格しました。params と stream の命名、文字列リテラルのステータス、依存リソースのための新しい chain() ヘルパーはすべて本番環境で使用可能です。Angular 20 または 21 からアップグレードする場合、API 表面は互換性を維持しています。

Angular が提供する3種類の Resource バリアント

Angular は、非同期データを Signal として読み込むための3つの手段を提供しています。それぞれ異なるユースケースを対象としていますが、共通のリアクティブモデルを採用しています。依存関係を宣言し、ローダーを定義し、結果を Signal を通じて消費するという流れです。

  • resource() は Promise と連携します。fetch() やその他の Promise ベースの API を使用する場合に適しています。
  • rxResource() は Observable と連携します。debounceTime、retry、switchMap などの RxJS オペレータが必要な場合に選択されます。
  • httpResource() は Angular の HttpClient を直接ラップします。インターセプター、テストユーティリティ、スキーマバリデーションが追加の設定なしで機能します。

httpResource と他の2つの主要な違いは、内部で HttpClient を使用する点にあります。これにより、既存のインターセプターがそのまま適用されます。Angular 19 では resource() が HttpClient を完全に迂回していたため、インターセプターが機能しないという課題がありました。

resource() によるユーザープロファイルの構築

resource() 関数は、params の計算値と loader 関数を受け取ります。params 内で読み取られた Signal の値が変化すると、loader が自動的に再実行されます。

user-profile.component.tstypescript
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 として公開します。

product-list.component.tstypescript
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 は読み取り専用です

httpResource はデータの取得(GET リクエスト)専用に設計されています。POST、PUT、DELETE の操作に使用することは安全ではありません。リクエストのキャンセルにより、書き込み処理が途中で中断される可能性があるためです。書き込み操作には HttpClient を直接使用するか、サービスメソッドでミューテーションをラップする必要があります。

chain() を使用したリソースの連鎖

Angular 22 では、依存関係のあるリソースのローディングを行う chain() ヘルパーが導入されました。あるリソースが別のリソースの値に依存する場合、chain() はローディング状態とエラー状態を自動的に伝播します。

user-posts.component.tstypescript
import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';

interface User {
  id: number;
  name: string;
  authorId: string;
}

interface Post {
  id: number;
  title: string;
  content: string;
}

@Component({
  selector: 'app-user-posts',
  template: \`
    @if (postsResource.isLoading()) {
      <p>Loading...</p>
    } @else if (postsResource.hasValue()) {
      <h2>Posts by {{ userResource.value().name }}</h2>
      @for (post of postsResource.value(); track post.id) {
        <article>
          <h3>{{ post.title }}</h3>
        </article>
      }
    } @else if (postsResource.error()) {
      <p>Failed to load posts</p>
    }
  \`,
})
export class UserPostsComponent {
  userId = signal(1);

  userResource = httpResource<User>(() => \`/api/users/\${this.userId()}\`);

  // chain() propagates userResource's status to postsResource
  postsResource = httpResource<Post[]>({
    params: ({ chain }) => {
      const user = chain(this.userResource);
      return { url: '/api/posts', params: { authorId: user.authorId } };
    },
  });
}

chain(userResource) は単に値を返すだけではありません。userResource がローディング中の間、postsResource もローディング状態を報告し、ローダーは実行されません。userResource がエラーになった場合、postsResource は上流のエラーを含む ResourceDependencyError とともにエラー状態に遷移します。

このパターンは、ネストされた switchMap 呼び出しの RxJS パターンを、明示的で正しい動作がデフォルトとなるステータス伝播に置き換えます。

Zod と httpResource によるスキーマバリデーション

外部サービスからの API レスポンスは、想定されたデータ構造からずれることがあります。httpResource の parse オプションは、Zod のようなスキーマバリデーションライブラリと統合し、不正なデータを暗黙的に伝播させるのではなく、ランタイムで不一致を検出できるようにします。

order.component.tstypescript
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 型も決定するため、スキーマ定義はランタイムバリデーターと型ジェネレーターの二重の役割を果たします。

rxResource の stream と params

Angular では、rxResource において loader が stream に、request が params にそれぞれ改名されています。この変更は、rxResource がサポートするストリーミングセマンティクスと命名を整合させるためです。stream 関数は Observable コンテキストを受け取り、Observable を返す必要があります。

search.component.tstypescript
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((.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、技術テストで練習しましょう。

ステータス追跡:文字列リテラル

ResourceStatus は文字列ユニオン型です。6つの値により、resource のライフサイクルをきめ細かく把握できます。

ステータス意味
'idle'params が undefined を返した。リクエストは未発行
'loading'初回リクエストが実行中
'reloading'前回の成功後に後続のリクエストが実行中
'resolved'value() でデータが利用可能
'error'リクエストが失敗。error() にエラーが格納
'local'.set()または.update()` でローカルに値が設定された状態
status-demo.component.tstypescript
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 の実行が抑止されます。このパターンは、条件付きデータフェッチに適しています。ユーザーが入力を行うまでプロンプトを表示し、入力後にデータを読み込むといったフローを実現できます。

error 状態での value() アクセスについて

'error' ステータスにある resource の value() を呼び出すとランタイム例外がスローされます。値の読み取りは必ず hasValue() でガードするか、status() を事前に確認する必要があります。Angular 19 では value() がエラー時に undefined を返していたため、この変更は破壊的変更に該当します。

Angular 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 型を決定するため、単一のスキーマ定義からランタイムの安全性とコンパイル時の型の両方が得られます。

Q: chain() は上流リソースのエラーをどのように処理しますか?

リソースの params 関数内で chain(upstreamResource) を使用すると、上流リソースがローディング中、idle、またはエラー状態の場合、依存リソースのローダーは実行されません。上流リソースがエラーになった場合、依存リソースは ResourceDependencyError とともにエラー状態に遷移します。このエラーには dependency(上流リソース)と cause(実際の上流エラー)が含まれており、「自分のリクエストが失敗した」と「依存先のリクエストが失敗した」を区別できます。

Angular 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<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 宣言に置き換えるだけで完了します。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

Sources

Angular Resource API の重要ポイント

  • httpResource は、手動の HttpClient サブスクリプションを、ローディング・エラー・キャンセルを自動処理する単一のリアクティブ宣言に置き換えます
  • Promise ベースの API には resource()、RxJS オペレータを用いた Observable パイプラインには rxResource()、インターセプターサポート付きの標準的な HTTP 呼び出しには httpResource() を使用します
  • chain() ヘルパー(Angular 22 以降)は、自動的なステータス伝播による依存リソースのローディングを可能にします。上流がエラーになった場合、依存先は ResourceDependencyError を受け取ります
  • ステータス値は文字列リテラル('idle'、'loading'、'resolved'、'error'、'reloading'、'local')です
  • httpResource の parse オプションにより、Zod や Valibot を利用したランタイムスキーマバリデーションが可能になり、TypeScript 型の生成も兼ねます
  • error 状態の resource で value() を呼び出すと例外がスローされます。必ず hasValue() でガードするか status() を事前に確認してください
  • 3つの resource バリアントすべてが、依存関係の変更時に実行中のリクエストを自動キャンセルし、手動のキャンセルロジックなしでレースコンディションを防止します
今日のチャレンジ

Angular のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年10月13日 更新

タグ

#angular
#angular-20
#resource-api
#httpResource
#signals

共有

関連記事