From 0fc837795216a01414b33bda39ff7bfb138642e9 Mon Sep 17 00:00:00 2001 From: Saurabh Singh Date: Sun, 14 Jun 2026 23:06:09 +0530 Subject: [PATCH 1/5] docs(core): document resource chaining with chain() in params context Adds a new 'Chaining resources' section to the resource guide covering: - Basic usage of chain() to depend one resource on another - Automatic status propagation (loading/idle/error) - Chaining vs reading .value() directly - Synchronous value transformation via chained resource Closes #69329 --- adev/src/content/guide/signals/resource.md | 62 ++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/adev/src/content/guide/signals/resource.md b/adev/src/content/guide/signals/resource.md index 5ff6b7ca589f..c173cd141483 100644 --- a/adev/src/content/guide/signals/resource.md +++ b/adev/src/content/guide/signals/resource.md @@ -140,6 +140,68 @@ The `id` value must be unique within your application and identical on the serve 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. + +```typescript +import {resource} from '@angular/core'; + +const userResource = resource({ + params: () => ({id: getUserId()}), + loader: ({params}) => fetchUser(params), +}); + +const userPostsResource = resource({ + params: ({chain}) => ({userId: chain(userResource).id}), + loader: ({params}) => fetchPostsForUser(params.userId), +}); +``` + +`chain(userResource)` returns the resolved value of `userResource` and automatically propagates its status to `userPostsResource`: + +- If `userResource` is **loading**, `userPostsResource` also enters the `loading` state and its loader does not run. +- If `userResource` is **idle**, `userPostsResource` also becomes `idle`. +- If `userResource` is in an **error** state, `userPostsResource` also enters the `error` state. +- If `userResource` is **resolved**, `chain` returns its value so `userPostsResource` can use it as params. + +This means you never need to manually guard against `undefined` when chaining — the status propagates automatically. + +### Chaining vs. reading resource values directly + +You might be tempted to read a resource's value directly inside `params`: + +```typescript +// Avoid: reads value() directly without status propagation +const userPostsResource = resource({ + params: () => { + const user = userResource.value(); // may be undefined + return user ? {userId: user.id} : undefined; + }, + loader: ({params}) => fetchPostsForUser(params.userId), +}); +``` + +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. + +### Synchronously transforming a resource value + +`chain` is also useful when you want to map a resource's resolved value synchronously while the loader remains async: + +```typescript +const numericResource = resource({ + params: () => ({}), + loader: async () => 42, +}); + +const stringResource = resource({ + params: ({chain}) => ({value: chain(numericResource)}), + loader: async ({params}) => `The answer is ${params.value}`, +}); +``` + +Note that `stringResource` will only start loading once `numericResource` has resolved. If you only need a synchronous transformation and no additional async work, prefer `computed` over a chained resource. + ## Reactive data fetching with `httpResource` [`httpResource`](/guide/http/http-resource) is a wrapper around `HttpClient` that gives you the request status and response as signals. It makes HTTP requests through the Angular HTTP stack, including interceptors. From bb14db907ec31630ea9f4b65351dbd38bf72b311 Mon Sep 17 00:00:00 2001 From: Saurabh Singh Date: Mon, 15 Jun 2026 00:13:48 +0530 Subject: [PATCH 2/5] docs(core): fix chain() example to avoid accessing property on undefined Pass the chained value directly as params so when chain() returns undefined the downstream resource becomes idle rather than throwing. Addresses review feedback from @JeanMeche on PR #69348. --- adev/src/content/guide/signals/resource.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adev/src/content/guide/signals/resource.md b/adev/src/content/guide/signals/resource.md index c173cd141483..516b8e18b726 100644 --- a/adev/src/content/guide/signals/resource.md +++ b/adev/src/content/guide/signals/resource.md @@ -153,8 +153,8 @@ const userResource = resource({ }); const userPostsResource = resource({ - params: ({chain}) => ({userId: chain(userResource).id}), - loader: ({params}) => fetchPostsForUser(params.userId), + params: ({chain}) => chain(userResource)?.id, + loader: ({params: userId}) => fetchPostsForUser(userId), }); ``` @@ -165,7 +165,7 @@ const userPostsResource = resource({ - If `userResource` is in an **error** state, `userPostsResource` also enters the `error` state. - If `userResource` is **resolved**, `chain` returns its value so `userPostsResource` can use it as params. -This means you never need to manually guard against `undefined` when chaining — the status propagates automatically. +Note that `chain` may return `undefined` when the upstream resource value is `undefined`. Passing the value directly as params (rather than wrapping it in an object) means `userPostsResource` will become `idle` when the upstream has no value, which is the correct behaviour. ### Chaining vs. reading resource values directly From 5b436c11014149688513cc9a29d97f62ee64dc18 Mon Sep 17 00:00:00 2001 From: Saurabh Singh Date: Mon, 15 Jun 2026 00:20:49 +0530 Subject: [PATCH 3/5] docs(core): remove misleading synchronous transformation example The example used an async resource to do synchronous string mapping then recommended computed instead, which was confusing. Replaced with a concise note clarifying when to reach for chain vs computed. Addresses review feedback from @JeanMeche on PR #69348. --- adev/src/content/guide/signals/resource.md | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/adev/src/content/guide/signals/resource.md b/adev/src/content/guide/signals/resource.md index 516b8e18b726..9a64db505c23 100644 --- a/adev/src/content/guide/signals/resource.md +++ b/adev/src/content/guide/signals/resource.md @@ -184,23 +184,7 @@ const userPostsResource = resource({ 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. -### Synchronously transforming a resource value - -`chain` is also useful when you want to map a resource's resolved value synchronously while the loader remains async: - -```typescript -const numericResource = resource({ - params: () => ({}), - loader: async () => 42, -}); - -const stringResource = resource({ - params: ({chain}) => ({value: chain(numericResource)}), - loader: async ({params}) => `The answer is ${params.value}`, -}); -``` - -Note that `stringResource` will only start loading once `numericResource` has resolved. If you only need a synchronous transformation and no additional async work, prefer `computed` over a chained resource. +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. ## Reactive data fetching with `httpResource` From 8e424617936f3571092ee1fcff218713eefc9c81 Mon Sep 17 00:00:00 2001 From: Saurabh Singh Date: Mon, 15 Jun 2026 00:50:21 +0530 Subject: [PATCH 4/5] docs(core): align chaining explanation with the updated example Reword the description so it matches chain(userResource)?.id being passed directly as params, and explain why returning the value directly (instead of wrapping it in an object) keeps the idle behaviour. Addresses review feedback from @JeanMeche on PR #69348. --- adev/src/content/guide/signals/resource.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adev/src/content/guide/signals/resource.md b/adev/src/content/guide/signals/resource.md index 9a64db505c23..c194d7414161 100644 --- a/adev/src/content/guide/signals/resource.md +++ b/adev/src/content/guide/signals/resource.md @@ -158,14 +158,14 @@ const userPostsResource = resource({ }); ``` -`chain(userResource)` returns the resolved value of `userResource` and automatically propagates its status to `userPostsResource`: +`chain(userResource)` reads the value of `userResource` and automatically propagates its status to `userPostsResource`: - If `userResource` is **loading**, `userPostsResource` also enters the `loading` state and its loader does not run. - If `userResource` is **idle**, `userPostsResource` also becomes `idle`. - If `userResource` is in an **error** state, `userPostsResource` also enters the `error` state. -- If `userResource` is **resolved**, `chain` returns its value so `userPostsResource` can use it as params. +- If `userResource` is **resolved**, `chain` returns its value, which `userPostsResource` then uses as its params. -Note that `chain` may return `undefined` when the upstream resource value is `undefined`. Passing the value directly as params (rather than wrapping it in an object) means `userPostsResource` will become `idle` when the upstream has no value, which is the correct behaviour. +A resource's value can be `undefined`, so `chain` can return `undefined` too. The example passes `chain(userResource)?.id` directly as the params value, so when there is no upstream value the params are `undefined` and `userPostsResource` becomes `idle`. Returning the value directly is important here: a params value like `{userId: undefined}` is still defined, so it would run the loader with an `undefined` id instead. ### Chaining vs. reading resource values directly From 66fef4eeb2957059ea43960bedda5cfc2af1dcae Mon Sep 17 00:00:00 2001 From: Saurabh Singh Date: Mon, 15 Jun 2026 06:59:42 +0530 Subject: [PATCH 5/5] docs(core): clarify chain throws when not resolved, move object note to callout Clarify that chain throws to propagate status when the upstream resource is not resolved, and only returns undefined when the resolved value is undefined. Move the direct-value vs object params guidance into its own NOTE callout. Addresses review feedback from @JeanMeche on PR #69348. --- adev/src/content/guide/signals/resource.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adev/src/content/guide/signals/resource.md b/adev/src/content/guide/signals/resource.md index c194d7414161..95f9721465c1 100644 --- a/adev/src/content/guide/signals/resource.md +++ b/adev/src/content/guide/signals/resource.md @@ -165,7 +165,9 @@ const userPostsResource = resource({ - If `userResource` is in an **error** state, `userPostsResource` also enters the `error` state. - If `userResource` is **resolved**, `chain` returns its value, which `userPostsResource` then uses as its params. -A resource's value can be `undefined`, so `chain` can return `undefined` too. The example passes `chain(userResource)?.id` directly as the params value, so when there is no upstream value the params are `undefined` and `userPostsResource` becomes `idle`. Returning the value directly is important here: a params value like `{userId: undefined}` is still defined, so it would run the loader with an `undefined` id instead. +When `userResource` is not yet resolved, `chain` throws to propagate its status, so the params function does not continue. When `userResource` is resolved, `chain` returns its value, which can itself be `undefined` if that's the resolved value. The example handles this with `chain(userResource)?.id`, so an `undefined` value results in `undefined` params and `userPostsResource` becomes `idle`. + +NOTE: Pass the chained value directly as the params value rather than wrapping it in an object. A params value like `{userId: undefined}` is still a defined value, so the loader would run with an `undefined` id instead of the resource becoming `idle`. ### Chaining vs. reading resource values directly