-
-
Couldn't load subscription status.
- Fork 2k
Description
Important: withResource is not planned for v20.0 release. It becomes valid once Resource leaves the experimental status.
Which @ngrx/* package(s) are relevant/related to the feature request?
signals
Information
This proposal introduces a new SignalStore feature: withResource. It enables integrating any ResourceRef into a SignalStore instance, allowing store authors to declaratively manage resource state, status, and control methods like reload alongside their local state and methods.
Motivation
It is currently difficult to integrate a ResourceRef into a SignalStore in a clean and structured way. While it is possible to attach a resource manually via withProps, this approach does not provide full integration: resource members like value, status, or reload are not naturally part of the store’s API, nor do they follow consistent naming or lifecycle handling.
Given the relative newness of SignalStore, a large portion of current users are early adopters. These developers are also actively experimenting with newer features like resource, httpResource, and rxResource. Right now, they are faced with a trade-off: either use SignalStore or take full advantage of resource APIs.
By providing deep integration through withResource, developers no longer have to choose.
Design
withResource accepts a callback that takes state signals, props, and computed signals as an input argument and returns a ResourceRef (Unnamed Resource) or a dictionary of ResourceRef (Named Resources).
Basic Usage (Unnamed Resource)
When a single resource is provided, its members (value, status, error, hasValue, etc.) are merged directly into the store instance. This makes the store itself conform to the Resource<T> interface.
The value is stored as part of the SignalStore’s state under the value key. This means:
- It can be updated via
patchState()like any other state property. - It is exposed as a
DeepSignal.
The reload method is added as a private _reload() method to prevent external use. Only the SignalStore itself should be able to trigger reloads.
const UserStore = signalStore(
withState({ userId: undefined as number | undefined }),
withResource(({ userId }) =>
httpResource<User>(() =>
userId === undefined ? undefined : `/users/${userId}`
)
)
);
const userStore = new UserStore();
userStore.value(); // User | undefined
userStore.status(); // 'resolved' | 'loading' | 'error' | ...
userStore.error(); // unknown | Error
userStore.hasValue(); // boolean
patchState(userStore, { value: { id: 1, name: 'Test' } });Named Resources (Composition)
To support multiple resources within a store, a Record<string, ResourceRef> can be passed to withResource. Each resource will be prefixed using its key.
All members (value, status, error, hasValue, etc.) are added to the store using the resource key as a prefix. Each value is stored in the state and exposed as a DeepSignal, just like in the unnamed case.
const UserStore = signalStore(
withState({ userId: undefined as number | undefined }),
withResource(({ userId }) => ({
list: httpResource<User[]>(() => '/users', { defaultValue: [] }),
detail: httpResource<User>(() =>
userId === undefined ? undefined : `/users/${userId}`
),
}))
);
const userStore = new UserStore();
userStore.listValue(); // User[]
userStore.listStatus(); // 'resolved' | 'loading' | 'error'
userStore.detailError(); // unknown | Error
userStore.detailHasValue(); // boolean
patchState(userStore, { listValue: [] });Each named resource also receives a prefixed reload method, e.g. _listReload() or _detailReload(), which are intentionally private and intended for internal store logic only.
Interoperability with Resource<T>
Named resources don’t directly conform to the Resource<T> type, but they can be mapped back using the mapToResource() utility:
const detailResource = mapToResource(userStore, 'detail');
use(detailResource); // Resource<User | undefined>This enables compatibility with existing APIs or utilities that expect a Resource<T>.
Describe any alternatives/workarounds you're currently using
No response
I would be willing to submit a PR to fix this issue
- Yes
- No