Angular 20 Resource API 完全ガイド:httpResource・rxResource と技術面接の質問

Angular 20 で安定版となった Resource API を徹底解説。resource()、httpResource()、rxResource() によるリアクティブデータフェッチ、Zod バリデーション、ResourceStatus の状態管理、HttpClient からの移行方法、技術面接の頻出質問まで、実践的なコード例とともに網羅します。

Angular 20 Resource API と httpResource の完全ガイド

Angular 20 では、Resource API がリアクティブなデータフェッチの中核メカニズムとして安定版に昇格しました。Angular 19 で実験的に導入された resource()rxResource()httpResource() という3つのプリミティブは、バージョン 20 において正式な安定 API となり、Signal システムと統合された宣言的なデータ取得モデルを提供します。Resource API により、手動でのサブスクリプション管理、ローディング状態の追跡、コンポーネントライフサイクルの管理といった、Angular アプリケーションにおいて歴史的に複雑さの原因となっていた課題が解消されます。

本記事では、Resource API の3つのバリアントを詳細に分析し、具体的なコード例を用いた実装パターンを解説します。さらに、Zod によるランタイムバリデーション、ResourceStatus による状態管理、従来の HttpClient パターンからの移行手順、そして 2026 年の Angular 技術面接で頻出する質問と回答を体系的にまとめます。

Angular 20 での主な変更点

Angular 20 では Resource API が Developer Preview から安定版へ移行しました。バージョン 19 からの主な変更点として、パラメータ名の変更があります。requestparams に、loader / stream はそれぞれ resource()rxResource() の新しいコントラクト名に変更されています。また、httpResourceparse オプションによるネイティブバリデーションをサポートし、ResourceStatus 型は数値の enum ではなく文字列リテラル('loading''resolved''error' など)を使用するようになりました。Angular 19 からの移行ではシンタックスの更新が必要ですが、基本的なセマンティクスは変わりません。

resource(): Promise ベースの基本プリミティブ

resource() は Resource API の基盤となるプリミティブであり、Promise ベースの非同期データ取得に使用されます。リアクティブな依存関係を定義する params 関数と、データを取得する loader 関数を受け取ります。params が返す値が変化するたびに、loader が自動的に再実行され、前回のリクエストは AbortSignal を通じてキャンセルされます。

以下の例では、ユーザープロファイルをリアクティブに読み込むコンポーネントを示します。Signal userId がリアクティブな依存関係として機能し、その値が変更されるたびに自動的にデータの再取得が開始されます。

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
  }
}

このパターンの核心は、リアクティブな依存関係とデータ取得ロジックの分離にあります。params 関数はフレームワークのリアクティブコンテキスト内で実行されるため、関数内で読み取られた Signal は自動的に追跡されます。userId が変更されると、Angular は loader に渡された abortSignal を通じて実行中のリクエストをキャンセルし、新しいリクエストを開始します。コンポーネント内に明示的なクリーンアップコードは一切必要ありません。

Resource API の基盤となる Signal システムの詳細については、Angular Signals のガイドを参照してください。

httpResource(): ボイラープレート不要のデータフェッチ

httpResource() は、Angular の HttpClient 上に構築された高レベルの抽象化です。URL またはリクエスト設定オブジェクトをリアクティブ関数として受け取り、loader 関数を明示的に記述する必要がありません。フレームワークが内部的に HTTP リクエストを構築し、アプリケーションに設定されたインターセプターとも自動的に統合されます。

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
  }
}

ユーザーが filterByCategory() を呼び出すと、Signal category が更新されます。httpResource に渡された設定関数は this.category() を読み取っているため、フレームワークは変更を検知し、実行中の HTTP リクエストを自動的にキャンセルして、更新されたパラメータで新しいリクエストを開始します。この動作は RxJS の switchMap に類似していますが、オペレータの構文的な複雑さはありません。

なお、httpResource は読み取り専用(GET)の操作専用として設計されています。POST、PUT、DELETE などの書き込み操作には対応していません。書き込み操作には引き続き HttpClient を直接使用する必要があります。.set().update() を呼び出した場合、Signal のローカル値のみが変更され、サーバーへのリクエストは送信されません。

Zod と parse オプションによるランタイムバリデーション

フロントエンドのデータフェッチにおいて繰り返し発生する問題の一つが、期待されるデータ構造とサーバーから実際に返却されるデータ構造の不一致です。Angular 20 では、httpResourceparse オプションを通じて、バリデーションメカニズムを直接統合することでこの問題に対処しています。

Zod との統合により、期待されるスキーマを定義し、すべてのレスポンスをテンプレートに公開する前に自動的に検証できます。バリデーションが失敗した場合、resource は error 状態に遷移し、説明的なエラーメッセージが格納されるため、不正なデータのレンダリングを防止できます。

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 が別チームによって開発されており、レスポンス構造が事前の調整なしに変更される可能性があるエンタープライズ環境で特に価値を発揮します。Zod によるランタイムバリデーションは、フロントエンドとバックエンド間の安全契約として機能し、非互換性を即座に可視化します。

rxResource(): RxJS とのネイティブ統合

RxJS のオペレータが必要なシナリオ(例えばデバウンス付き検索フィールド)では、rxResource() が Observable ベースのインターフェースを提供します。stream 関数(Angular 20 で loader から改名)はリアクティブパラメータを受け取り、フレームワークが内部で管理する 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 },
      }),
  });
}

rxResourceresource の根本的な違いは、データ取得関数の戻り値の型にあります。Promise ではなく Observable を返します。内部的に Angular がサブスクリプションの管理、パラメータ変更時の自動キャンセル、Change Detection サイクルとの同期を処理します。既に RxJS と HttpClient を広範に使用しているコードベースにとって、rxResource は最も自然な移行パスとなります。

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

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

ResourceStatus: ローディング状態のきめ細かな管理

すべての resource インスタンスは、現在の読み込み状態を示す文字列リテラルを返す status() Signal を公開します。このモデルは以前の数値 enum ベースのアプローチを置き換え、コードの可読性とテンプレートの @switch 構文との互換性を向上させています。

| Status | 意味 | |---|---| | 'idle' | paramsundefinedを返した — リクエスト未発行 | | 'loading' | 初回リクエスト実行中 | | 'reloading' | 前回成功後の再リクエスト実行中 | | 'resolved' | データがvalue()で利用可能 | | 'error' | リクエスト失敗 — error()にエラー格納 | | 'local' | .set()または.update()でローカルに設定された値 |

'loading''reloading' の区別により、差別化された UX パターンを実装できます。初回読み込み時にはフルスクリーンスピナーを表示し、後続の更新時には控えめなインジケータを表示して、既に表示されているデータを維持することが可能です。

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();
    },
  });
}

'idle' ステータスは特に注目に値します。params 関数が undefined を返した場合、resource はリクエストを一切発行しません。このパターンにより、ユーザーがフィルタやカテゴリの選択など特定のアクションを実行したときにのみデータを取得する条件付きローディングを実装できます。

error 状態での value() の挙動に注意

value() メソッドは、resource が 'error' 状態にあるときに呼び出すと例外をスローします。安全に値にアクセスするには、テンプレート内で hasValue() を条件ガードとして使用するか、データが存在しない場合に undefined を返す value.asReadonly() メソッドを使用します。この設計上の選択は、エラー状態の明示的な処理を強制し、不正なデータの意図しないレンダリングを防止するためのものです。

ngOnInit + subscribe パターンからの移行

Resource API は、従来の HttpClient への手動サブスクリプションパターンと比較して、データフェッチに必要なコード量を大幅に削減します。以下の比較は、ボイラープレートの削減とライフサイクル管理の明示的な処理の排除を示しています。

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()
}

「Before」のコンポーネントでは、リクエストを開始するための OnInit の実装、キャンセル用の Subject、データ・ローディング・エラーの個別の状態変数、メモリリークを防ぐための OnDestroy が必要です。「After」のコンポーネントは、このすべてのロジックを宣言的な一行に凝縮しています。フレームワークが内部的にリクエストの開始、状態の追跡、キャンセル、コンポーネント破棄時のクリーンアップを管理します。

この移行パターンは、数百のコンポーネントが ngOnInit + subscribe + takeUntil パターンに従っている大規模コードベースにおいて特に効果的です。移行はコンポーネント単位で段階的に実施でき、アプリケーションの他の部分に影響を与えません。Angular コンポーネントの移行戦略については、Standalone Components への移行の記事も参考にしてください。

Angular 20 Resource API 技術面接の質問

以下の質問は、2026 年の Angular ポジションにおける技術面接で頻繁に出題されるトピックを網羅しています。各回答は、Resource API に対する実践的な理解を示すために十分な深さを提供します。

Q1: Angular 20 における resource()、rxResource()、httpResource() の違いは何ですか?

resource()Promise を返す loader 関数を受け取る基本プリミティブです。rxResource() は RxJS 向けのバリアントであり、stream 関数が Observable を返します。httpResource() は内部的に HttpClient を使用する高レベルの抽象化であり、URL とパラメータの設定のみを必要とします。3つのバリアントすべてが params 関数を通じてリアクティブな依存関係を自動的に追跡し、パラメータ変更時に保留中のリクエストをキャンセルし、value()status()error()isLoading() などの Signal を通じて状態を公開します。選択基準は次のとおりです。ほとんどのケースでは httpResource、非 HTTP API には resource、RxJS オペレータが必要なロジックには rxResource を使用します。

Q2: Resource API はリクエストの自動キャンセルをどのように処理しますか?

params() が返す値が変更されると、フレームワークは実行中のリクエストを自動的にキャンセルします。resource() の場合、loader 関数に渡された AbortSignal を通じて、基盤となる fetch() の中断がシグナルされます。rxResource() の場合、フレームワークは新しいサブスクリプションを作成する前に、現在の Observable へのサブスクリプションを解除します。この動作は switchMap に類似しています。httpResource() の場合、キャンセルは HttpClient によって内部的に管理されます。このメカニズムにより、低速なレスポンスがより新しいレスポンスを上書きしてしまう「stale response override」として知られるレースコンディションが防止されます。

Q3: params() 関数が undefined を返した場合、どのような動作になりますか?

params()undefined を返すと、resource は 'idle' 状態に入り、HTTP リクエストは一切発行されません。これは条件付きローディングパターンです。必要な条件が満たされるまで resource は非アクティブのままとなります。例えば、詳細コンポーネントは要素が選択されていない場合に undefined を返し、ユーザーが選択を行った場合にのみデータの取得を開始できます。'idle' 状態は 'loading' とは明確に区別されます。params() が定義済みの値を返すまで、リクエストは実行中でも予定されてもいません。

Q4: Zod バリデーションは httpResource とどのように統合されますか?

httpResourceparse オプションは、HTTP レスポンスに対して value() Signal を通じて値が公開される前に実行されるバリデーション関数を受け取ります。OrderSchema.parseparse の値として渡すことで、すべてのレスポンスが定義された Zod スキーマに対してバリデーションされます。バリデーションが失敗すると、resource は 'error' 状態に遷移し、error() には違反の詳細を含む ZodError オブジェクトが格納されます。このパターンは、フロントエンドとバックエンド間のランタイム契約を導入し、独立したチームが開発し、スキーマが事前の調整なしに変更される可能性がある API において特に有用です。

Q5: ResourceStatus の 'loading' と 'reloading' の違いは何ですか?

'loading' は初回リクエストが実行中であり、resource に以前のデータが存在しないことを示します。'reloading' は、以前の成功後に新しいリクエストが開始されたことを示します。前回の値は新しいリクエスト中も value() を通じてアクセス可能です。この区別により、差別化された UX パターンを実装できます。初回ロード時にはスケルトンローダーやフルスクリーンスピナーを表示し、リロード時にはプログレスバーや回転アイコンなどの控えめな更新インジケータを表示して、既に表示されているデータを維持し、空コンテンツのフラッシュを回避することが可能です。

Q6: httpResource は書き込み操作(POST、PUT、DELETE)に使用できますか?

httpResource は読み取り専用の GET 操作専用として設計されており、POST、PUT、DELETE などの HTTP メソッドには対応していません。書き込み操作には引き続き HttpClient を直接使用するか、Server Actions を利用する必要があります。resource インスタンスで .set().update() を呼び出した場合、Signal のローカル値のみが変更され、サーバーへの HTTP リクエストは一切送信されません。この制約は、Resource API がデータの読み取りと状態の追跡に特化した設計であることに起因しています。

Angular の Signal や SSR に関するさらなる面接質問については、Angular 19 面接質問ガイドの記事も参照してください。

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

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

まとめ

Angular 20 の Resource API は、Angular アプリケーションにおけるデータフェッチのパラダイムシフトを象徴しています。要点を整理すると次のとおりです。

  • resource() は Promise ベースの非同期データフェッチの基本プリミティブであり、AbortSignal による自動キャンセルとリアクティブな依存関係の追跡を提供します
  • httpResource() は HttpClient 上の高レベル抽象化であり、GET 操作のボイラープレートコードを排除し、インターセプターやヘッダーとネイティブに統合されます
  • rxResource() は debounceTime や retry などのオペレータが必要なシナリオのために、Promise の代わりに Observable を使用した RxJS との互換性を維持します
  • Zod バリデーションparse オプションを通じてフロントエンドとバックエンド間のランタイム契約を導入し、不正なデータのレンダリングを防止します
  • ResourceStatus の文字列リテラル('idle''loading''reloading''resolved''error''local')により、初回ロードと後続更新で差別化された UX パターンを実装できます
  • ngOnInit + subscribe + takeUntil パターンからの移行は段階的に実施でき、ボイラープレートコードとメモリリークの潜在的な原因を大幅に削減します

Angular の技術面接に臨むチームにとって、Resource API とその使用パターンの理解は 2026 年において基本的な要件です。3つのバリアントの違い、自動キャンセルメカニズム、スキーマバリデーションとの統合を説明できる能力は、Angular 20 のリアクティブアーキテクチャに対する成熟した理解を示すものとなります。

タグ

#angular
#angular-20
#resource-api
#httpresource
#signals
#interview
#typescript

共有

関連記事