すべてのシグナルAPIは同期的です(signal、computed、inputなど)。しかし、アプリケーションは多くの場合、非同期的に利用可能なデータを処理する必要があります。Resourceを使用すると、非同期データをアプリケーションのシグナルベースのコードに組み込み、そのデータに同期的にアクセスできるようになります。
Resourceを使用してあらゆる種類の非同期処理を実行できますが、Resourceの最も一般的なユースケースはサーバーからデータを取得することです。以下は、ユーザーデータを取得するためのリソースを作成する例です。
Resourceを作成する最も簡単な方法は、resource関数を使用することです。
import {computed, resource, Signal} from '@angular/core';
const userId: Signal<string> = getUserId();
const userResource = resource({
// Define a reactive computation.
// The params value recomputes whenever any read signals change.
params: () => ({id: userId()}),
// Define an async loader that retrieves data.
// The resource calls this function every time the `params` value changes.
loader: ({params}) => fetchUser(params),
});
// Create a computed signal based on the result of the resource's loader function.
const firstName = computed(() => {
if (userResource.hasValue()) {
// `hasValue` serves 2 purposes:
// - It acts as type guard to strip `undefined` from the type
// - It protects against reading a throwing `value` when the resource is in error state
return userResource.value().firstName;
}
// fallback in case the resource value is `undefined` or if the resource is in error state
return undefined;
});
resource関数は、2つの主なプロパティであるparamsとloaderを持つResourceOptionsオブジェクトを受け入れます。
paramsプロパティは、パラメータ値を生成するリアクティブな計算を定義します。この計算で読み取られるシグナルが変更されるたびに、リソースは新しいパラメータ値を生成します。これはcomputedと同様です。
loaderプロパティはResourceLoaderを定義します。これは、状態を取得する非同期関数です。リソースは、params計算が新しい値を生成するたびにローダーを呼び出し、その値をローダーに渡します。詳細は下記のResourceローダーを参照してください。
Resourceには、ローダーの結果を含むvalueシグナルがあります。
リソースローダー
リソースを作成する際には、ResourceLoaderを指定します。このローダーは、単一のパラメーター(ResourceLoaderParamsオブジェクト)を受け入れ、値を返す非同期関数です。
ResourceLoaderParamsオブジェクトには、params、previous、abortSignalの3つのプロパティが含まれています。
| プロパティ | 説明 |
|---|---|
params |
リソースのparams計算の値。 |
previous |
statusプロパティを含む、前のResourceStatusを持つオブジェクト。 |
abortSignal |
AbortSignal。詳細は下記のリクエストの中断を参照してください。 |
paramsの計算がundefinedを返す場合、ローダー関数は実行されず、リソースの状態はIdleになります。
Streaming resources
Some asynchronous data sources produce multiple values over time instead of returning a single result. Examples include WebSockets, Server-Sent Events (SSE), and Firestore onSnapshot listeners.
Use stream for these continuously updating data sources. Unlike loader, which resolves once for each request, stream returns a signal whose value can continue to update as new data becomes available.
Use loader for one-time asynchronous operations, such as fetching data from an HTTP endpoint.
const userUpdates = signal({value: 'Alice'});
const userResource = resource({
stream: () => userUpdates,
});
// Later, when new data arrives:
userUpdates.set({value: 'Bob'});
リクエストの中断
リソースが読み込み中の場合にparams計算が変更されると、リソースは未処理の読み込み処理を中断します。
ResourceLoaderParams内のabortSignalを使用して、中断されたリクエストに応答できます。例えば、ネイティブのfetch関数はAbortSignalを受け入れます。
const userId: Signal<string> = getUserId();
const userResource = resource({
params: () => ({id: userId()}),
loader: ({params, abortSignal}): Promise<User> => {
// 与えられた`AbortSignal`がリクエストの中断を示している場合、
// fetchは未処理のHTTPリクエストをキャンセルします。
return fetch(`users/${params.id}`, {signal: abortSignal});
},
});
AbortSignalによるリクエストのキャンセルについては、AbortSignal on MDNを参照してください。
再読み込み
reloadメソッドを呼び出すことで、プログラム的にリソースのloaderをトリガーできます。
const userId: Signal<string> = getUserId();
const userResource = resource({
params: () => ({id: userId()}),
loader: ({params}) => fetchUser(params),
});
// ...
userResource.reload();
リソースの状態
リソースオブジェクトには、非同期ローダーの状態を読み取るためのいくつかのシグナルプロパティがあります。
| プロパティ | 説明 |
|---|---|
value |
リソースの最新の値、または値が受信されていない場合はundefined。 |
hasValue |
リソースが値を持っているかどうか。 |
error |
リソースのローダーの実行中に発生した最新のエラー、またはエラーが発生していない場合はundefined。 |
isLoading |
リソースローダーが現在実行中かどうか。 |
status |
後述のリソースの特定のResourceStatus。 |
status シグナルは、文字列定数を使用してリソースの状態を説明する特定の ResourceStatus を提供します。
| ステータス | value() |
説明 |
|---|---|---|
'idle' |
undefined |
リソースに有効なリクエストがなく、ローダーが実行されていません。 |
'error' |
undefined |
ローダーの読み込みがエラーになりました。 |
'loading' |
undefined |
params 値の変更の結果としてローダーが実行中です。 |
'reloading' |
前の値 | リソースの reload メソッドの呼び出しの結果としてローダーが実行中です。 |
'resolved' |
解決された値 | ローダーが完了しました。 |
'local' |
ローカルに設定された値 | リソースの値が .set() または .update() を介してローカルに設定されています。 |
この状態情報を使用して、ローディングインジケーターやエラーメッセージなどのユーザーインターフェース要素を条件付きで表示できます。
Caching resource data with SSR
When an application renders on the server, a resource loader runs once to produce the initial HTML. During hydration, the browser normally runs the same loader again.
To reuse the server result, provide an id for the resource. Angular stores the resolved value in TransferState on the server and uses it on the client to initialize the resource in a 'resolved' state.
const userId: Signal<string> = getUserId();
const userResource = resource({
params: () => ({id: userId()}),
loader: ({params}) => fetchUser(params),
id: 'user-unique-id',
});
The id value must be unique within your application and identical on the server and the client so that Angular can match the cached entry to the resource that requested it.
IMPORTANT: Because the cached value is serialized into the page's HTML, avoid setting id on resources that load data specific to the user who triggered the server-side render, especially if the rendered HTML can be cached or shared between users.
Chaining resources
Sometimes one resource depends on the result of another. You can express this dependency using the chain function available in the params context object.
import {resource} from '@angular/core';
const userResource = resource({
params: () => ({id: getUserId()}),
loader: ({params}) => fetchUser(params),
});
const companyResource = resource({
params: ({chain}) => chain(userResource)?.companyId,
loader: ({params: companyId}) => fetchCompany(companyId),
});
Here companyResource depends on the user's companyId, which is only known once userResource has loaded. chain(userResource) reads the value of userResource and automatically propagates its status to companyResource:
- If
userResourceis idle,companyResourcealso becomesidle. - If
userResourceis loading or reloading,companyResourceenters theloadingstate and its loader does not run. Note that duringreloading,chaindoes not return the previously resolved value. - If
userResourceis in an error state,companyResourcealso enters theerrorstate. - If
userResourceis resolved or local,chainreturns its current value, whichcompanyResourcethen uses as its params.
When chain propagates a status from userResource (idle, loading, reloading, or error), the params function does not continue. When userResource is resolved or local, chain returns its value, which can itself be undefined. The example handles this with chain(userResource)?.companyId, so an undefined value results in undefined params and companyResource becomes idle.
NOTE: Pass the chained value directly as the params value rather than wrapping it in an object. A params value like {companyId: undefined} is still a defined value, so the loader would run with an undefined companyId instead of the resource becoming idle.
Chaining vs. reading resource values directly
You might be tempted to read a resource's value directly inside params:
reads value() directly without status propagation
const companyResource = resource({
params: () => {
const user = userResource.value(); // may be undefined
return user ? {companyId: user.companyId} : undefined;
},
loader: ({params}) => fetchCompany(params.companyId),
});
While this works, returning undefined from params makes the resource go idle rather than reflecting the actual state of the upstream resource. Using chain is preferred because it correctly mirrors loading and error states.
Reach for chain only when the downstream resource performs its own async work that depends on the upstream value. If you only need to derive a value synchronously from a resource, use computed instead.
httpResource を使用したリアクティブデータ取得
httpResource は HttpClient のラッパーで、リクエストの状態とレスポンスをシグナルとして提供します。これはインターセプターを含むAngular HTTPスタックを通してHTTPリクエストを行います。
Resource composition with snapshots
A ResourceSnapshot is a structured representation of a resource's current state. Every resource has a snapshot property that provides a signal of its current state.
const userId: Signal<string> = getUserId();
const userResource = resource({
params: () => ({id: userId()}),
loader: ({params}) => fetchUser(params),
});
const userSnapshot = userResource.snapshot;
Each snapshot contains a status and either a value or an error.
Composing resources with snapshots
You can create new resources from snapshots using resourceFromSnapshots. This enables composition with signal APIs like computed and linkedSignal to transform resource behavior.
import {linkedSignal, resourceFromSnapshots, Resource, ResourceSnapshot} from '@angular/core';
function withPreviousValue<T>(input: Resource<T>): Resource<T> {
const derived = linkedSignal<ResourceSnapshot<T>, ResourceSnapshot<T>>({
source: input.snapshot,
computation: (snap, previous) => {
if (snap.status === 'loading' && previous && previous.value.status !== 'error') {
// When the input resource enters loading state, we keep the value
// from its previous state, if any.
return {status: 'loading' as const, value: previous.value.value};
}
// Otherwise we simply forward the state of the input resource.
return snap;
},
});
return resourceFromSnapshots(derived);
}
@Component({
/*... */
})
export class AwesomeProfile {
userId = input.required<number>();
user = withPreviousValue(httpResource(() => `/user/${this.userId()}`));
// When userId changes, user.value() keeps the old user data until the new one loads
}