Angular 20 の Resource API と httpResource を徹底解説:2026年の面接対策に必須の新機能

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

Angular 20 Resource API and httpResource tutorial

Angular 20 は、Signal ベースのデータフェッチにおいて Resource APIhttpResource を中心的な役割に据えました。これらの API は、従来の HttpClient サブスクリプションパターンに伴う冗長なコードを、ローディング・エラー・データ取得完了の各状態を自動追跡するリアクティブプリミティブに置き換えるものです。

Angular 20 での変更点

Resource API では、requestparams に、loaderstreamrxResource の場合)にそれぞれ改名されています。ステータス値は数値 enum から文字列リテラル('idle''loading''resolved''error''reloading''local')に変更されました。httpResourceHttpClient を基盤としており、インターセプターや Zod バリデーションを追加設定なしで利用できます。

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

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

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

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

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 は、valueisLoadingerrorstatusheaders を 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 を直接使用するか、サービスメソッドでミューテーションをラップする必要があります。

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

外部サービスからの API レスポンスは、想定されたデータ構造からずれることがあります。httpResourceparse オプションは、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 型も決定するため、スキーマ定義はランタイムバリデーターと型ジェネレーターの二重の役割を果たします。

Angular 20 における rxResource の stream と params

Angular 20 では、rxResource において loaderstream に、requestparams にそれぞれ改名されました。この変更は、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($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 パイプラインに対する完全な制御を提供します。debounceTimeretry のようなオペレータを stream 内でチェーンできます。ただし、最も一般的なケース(単一の GET リクエスト)においては、httpResource の方が少ないコードで実装できます。

Angularの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

ステータス追跡:文字列リテラルが Enum を置き換える

Angular 20 では、ResourceStatus が数値 enum から文字列ユニオン型に変更されました。6つの値により、resource のライフサイクルをきめ細かく把握できます。

| ステータス | 意味 | |---|---| | 'idle' | paramsundefined を返した。リクエストは未発行 | | '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() アクセスについて

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 より優先すべきケースはどのような場合ですか?

rxResourcestream 関数を通じて Observable の完全な制御を提供します。検索入力のデバウンス、指数バックオフによる失敗リクエストのリトライ、combineLatest による複数ストリームの結合など、RxJS オペレータが必要なデータパイプラインの場合に選択されます。標準的な GET リクエストであれば、httpResource の方が少ないコードで済みます。

Q: Signal が高速に変化した場合、Angular は並行リクエストをどのように処理しますか?

3つの resource バリアントすべてが、params が新しい値を生成した時点で保留中のリクエストをキャンセルします。httpResourceresource() では、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 への移行は、以下のような予測可能なパターンに従います。

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

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

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

まとめ

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

タグ

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

共有

関連記事