現在、Angularの新しい実験的API httpResource
が開発されている。これはv19.0でリリースされた実験的API resource
と関連する機能で、早ければ2月のv19.2、あるいは3月のv19.3で搭載されるだろう。まだリリース前だが、現時点での要点をかいつまんで紹介する。
Usage
httpResource
はひとことで言えば、「HttpClient
+ resource
の一般的なユースケースを簡略化するヘルパー関数」である。
使い方は次のようになるだろう。すでにresource
を使っている人からすればそれほど目新しくはない。httpResource
関数の戻り値はHttpResponseResource
型であり、これはResource
型のサブタイプである。なのでresource
関数の戻り値と同じように、isLoading
やvalue
といったシグナルを返すフィールドを持っている。シグナルなので、状態が変われば自動的にコンポーネントは再描画される。
@Component({
template: `
@if (data.isLoading()) {
<p>Loading</p>
}
@else {
{{ data.value() }}
}
`
})
export class App {
readonly data = httpResource<Data>('/api/data');
}
Request on Signal changes
第一引数にはHTTPリクエストを生成するための情報を渡す。文字列を渡せばURLとして扱われ、GETメソッドのリクエストが一度だけ送られる。関数を渡せば、その戻り値の文字列をURLとしてGETメソッドのリクエストが送られる。この関数はシグナルに対応しており、内包するシグナルの変更に反応してリクエストを再送信する。
たとえば、コンポーネントが親コンポーネントから受け取ったインプット値に対応したHTTPリクエストを送るなら次のようになる。
@Component({
template: `
@if (userData.isLoading()) {
<p>Loading</p>
}
@else {
{{ userData.value() }}
}
`
})
export class App {
readonly userId = input.required<number>();
readonly userData = httpResource<UserData>(
// this.userId が変わるたびにリクエストが送られて値が更新される
() => `/api/user/${this.userId()}`,
);
}
resource
関数のrequest
と同じように、この第一引数の関数がundefined
を返せばリクエストを送らずにキャンセルできる。初期状態ではリクエストせず追加のイベントを待つ場合に使われるだろう。
@Component({
template: `
@if (userData.isLoading()) {
<p>Loading</p>
}
@else {
{{ userData.value() }}
}
`
})
export class App {
readonly userId = signal<number>(-1);
readonly userData = httpResource<UserData>(
// undefinedを返すとリクエストが送信されない
() => this.userId() < 0 ? undefiend : `/api/user/${this.userId()}`,
);
}
HttpResourceRequest
あまり使わないと思われるが、GET以外のメソッドでHTTPリクエストを送ることもできる。文字列ではなくHttpResourceRequest
型のオブジェクトを第一引数に渡すことでリクエストの内容を細かく制御できる。このオプションはHttpClient
のrequest
メソッドの引数とほとんど同じである。オブジェクトを渡す場合も静的な値と関数の両方をサポートしている。
@Component(...)
export class App {
// POST /data?fast=yes + headers + body + credentials
readonly data = httpResource(
() => ({
url: '/data',
method: 'POST',
body: {message: 'Hello, backend!'},
headers: {
'X-Special': 'true',
},
params: {
'fast': 'yes',
},
withCredentials: true,
}),
);
}
Response Value Mapping
第二引数のmap
オプションでは、HTTPレスポンスボディに簡単な加工を加えてからvalue
シグナルに格納するよう変換関数を渡すことができる。たとえばJSONオブジェクトからなんらかのクラスインスタンスへの変換をしたり、zodのようなバリデーション関数を挟んだりできる。
@Component(...)
export class App {
readonly data = httpResource(`/api/user/${this.userId()}`, {
map: (data) => User.parse(data),
});
}
How it works
ソースコードを読めばわかるが、httpResource
は既存のHttpClient
とresource
を組み合わせただけのヘルパーだ。そのためHttpClient
のインターセプターも変わらず動作するし、逆に言えば provideHttpClient
でHttpClient
自体を利用可能にしていないと使えない。
また、resource
と同様に内部的にはeffect
に依存している。つまり、依存性の注入が行えるコンテキストでなければ呼び出せない。コンポーネントのフィールド初期化、コンストラクタであれば普通に使えるが、それ以外の場所では工夫が必要になる。ちなみに、第2引数のinjector
オプションにInjector
オブジェクトを渡せばそのコンテキストで動作するようになっている。
// 任意の注入コンテキストでhttpResourceを呼び出す
const res = httpResource('/data', { injector: TestBed.inject(Injector) });
Use-cases
ここまで見たように、httpResource
は結局HttpClient
でデータを解決するresource
を作成するため、まさにそのようなコードを書いていた部分ではボイラープレートを削減する助けになるだろう。HttpClient
のメソッドはObservable
を返すので、これまでは純粋なresource
ではなくrxResource
を使うか、いちいちPromise
に変換する必要があったが、httpResource
であればそのあたりを気にする必要はなくなる。
一方、これまでresource
と関係なくHttpClient
を使っていた処理をhttpResource
に書き換える必要があるかといえば、今のところは無いといっていいだろう。多くの場合はサービスクラスのメソッドでリクエストを送っていると思うが、そのような手続き的なコードからシグナルベースのリアクティブなコードに書き換えるのはなかなか骨が折れる大工事になる。
アプリケーション全体をリアクティブに書き換えていくことがあれば、resource
やhttpResource
を取り入れていくくらいの構えでいいだろう。resource
の活用には前提としてアプリケーションのシグナルベース化、リアクティブ化が必要である。
Conclusion
以上見てきたように、httpResource
はHttpClient
とresource
の組み合わせを簡略化する実験的APIだ。アプリケーションのリアクティブ化を進める中で、HTTPリクエストをシグナルベースで扱いたい場合に有用なツールとなるだろう。現時点では実験的な機能であるため、今後のAPIの変更には注意が必要だ。