Dev Study
← 解説「httpResource() — HTTP取得をさらに短く書く」に戻る

サンプルコードで身につける: httpResource() — HTTP取得をさらに短く書く

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1resource() + fetch との比較

同じ「ユーザー取得」をresource()とhttpResource()で書き比べます。loaderの定型がまるごと消え、URLを返す関数だけになります。読み取り側のvalue()やisLoading()は同じです。

// ── Before: resource() + fetch ──
// user = resource({
//   params: () => ({ id: this.userId() }),
//   loader: ({ params }) =>
//     fetch(`/api/users/${params.id}`).then((r) => r.json()),
// });

// ── After: httpResource() ──
// user = httpResource<User>(() => `/api/users/${this.userId()}`);

// 短くなるだけでなく、通信が HttpClient 経由になるので
// インターセプター(認証ヘッダー・共通エラー処理)が効く。
// 読み取りは同じ: user.value() / user.isLoading() / user.error()

2一覧と詳細 — シグナル変更で自動再取得

一覧で選んだIDのシグナルをURLの中で読んでいるので、選択が変わるだけで詳細が自動で取り直されます。subscribeも手動の再取得メソッドも書いていない点に注目してください。

import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';

type Article = { id: number; title: string; body: string };

@Component({
  selector: 'app-article-viewer',
  template:
    '<button (click)="selectedId.set(1)">記事1</button>' +
    '<button (click)="selectedId.set(2)">記事2</button>' +
    '@if (article.value(); as a) {' +
    '  <h2>{{ a.title }}</h2><p>{{ a.body }}</p>' +
    '}',
})
export class ArticleViewerComponent {
  selectedId = signal(1);

  // ボタンで selectedId が変わる → URLが変わる → 自動再取得
  article = httpResource<Article>(
    () => `/api/articles/${this.selectedId()}`,
  );
}

3クエリパラメータ付きのリクエスト

URL文字列の代わりにオブジェクトを返すと、paramsやheadersも指定できます。検索条件のシグナルをparamsに使えば、条件変更のたびに検索し直す画面がこれだけで書けます。

import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';

type Product = { id: number; name: string };

@Component({
  selector: 'app-product-search',
  template:
    '<input (input)="keyword.set($any($event.target).value)">' +
    '@for (p of results.value() ?? []; track p.id) {' +
    '  <p>{{ p.name }}</p>' +
    '}',
})
export class ProductSearchComponent {
  keyword = signal('');

  // オブジェクト形式なら params や headers も書ける
  results = httpResource<Product[]>(() => ({
    url: '/api/products',
    params: { q: this.keyword() },
  }));
}

4読み込み中・エラー・成功の3状態を出し分ける

httpResourceもresource()と同じ状態シグナルを持つので、テンプレートの出し分けはこれまでと同じ形です。error()にはHttpClient由来のエラー情報が入ります。

import { Component } from '@angular/core';
import { httpResource } from '@angular/common/http';

type News = { id: number; headline: string };

@Component({
  selector: 'app-news-list',
  template:
    '@if (news.isLoading()) {' +
    '  <p>読み込み中...</p>' +
    '} @else if (news.error()) {' +
    '  <p>取得に失敗しました <button (click)="news.reload()">再試行</button></p>' +
    '} @else {' +
    '  @for (n of news.value() ?? []; track n.id) { <p>{{ n.headline }}</p> }' +
    '}',
})
export class NewsListComponent {
  news = httpResource<News[]>(() => '/api/news');
  // reload() で同じ条件のまま取り直せるのも resource() と同じ
}

5取得は httpResource、更新は HttpClient

httpResourceは読み取り(GET)専用なので、保存や削除は従来通りHttpClientで行います。更新が成功したらreload()で一覧を取り直す、という組み合わせが基本の型です。

import { Component, inject } from '@angular/core';
import { HttpClient, httpResource } from '@angular/common/http';

type Todo = { id: number; title: string };

@Component({
  selector: 'app-todo-list',
  template:
    '@for (t of todos.value() ?? []; track t.id) { <p>{{ t.title }}</p> }' +
    '<button (click)="add()">追加</button>',
})
export class TodoListComponent {
  private http = inject(HttpClient);

  // 表示のための取得: httpResource
  todos = httpResource<Todo[]>(() => '/api/todos');

  // ユーザー操作による変更: HttpClient
  add() {
    this.http
      .post('/api/todos', { title: '新しいタスク' })
      .subscribe(() => this.todos.reload()); // 成功したら取り直す
  }
}