-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
Which @ngrx/* package(s) are relevant/related to the feature request?
signals
Information
Hi,
I believe I've found a better approach than the existing withFeature
function to implement custom SignalStore
features that depend on an input value from the store.
Currently, to add a custom feature that requires an input from the store, developers must manually use the withFeature
utility.
While this works, it introduces boilerplate and breaks the composability of features, since the custom feature cannot be passed directly into the signalStore
factory.
The goal is to make it possible to use custom signalStoreFeature
s in the same way as built-in functions like withState
, withMethods
, etc.
This improves readability and results in cleaner, more consistent code.
Here's a comparison between using withFeature
and the proposed withFeatureFactory
:
const filteredBooksFeature = (books: Signal<Book[]>) =>
signalStoreFeature(
withState({ query: '' }),
withComputed((store) => ({
filteredBooks: computed(() =>
books().filter((b) => b.name.includes(store.query()))
),
})),
withMethods((store) => ({
setQuery(query: string): void {
patchState(store, { query });
},
}))
);
const withBooksFilter = withFeatureFactory(filteredBooksFeature);
const BooksStore = signalStore(
withEntities<Book>(),
// ⚠️ withfeature
withFeature(({ entities }) => filteredBooksFeature(entities)), // 👈 NgRx example
// ✅ withFeatureFactory👇 full access to the store with type-safety
withBooksFilter(({ entities }) => entities)
);
The returned type of the inner function must match the type of the first parameter of the feature (in this case, filteredBooksFeature
).
This is fully type-safe and enforced by TypeScript.
However, there is one limitation: the custom feature (like filteredBooksFeature
) must accept exactly one parameter.
Otherwise, the inner function (withBooksFilter(({ entities }) => entities)
) would have to return an array, which would be semantically strange and less intuitive.
To avoid this, I chose to enforce that the feature factory only accepts a single parameter.
If multiple values need to be passed, they can be wrapped in a single object with multiple properties.
// ❌ Not allowed:
withFeatureFactory((signalA: A, signalB: B) => ...)
// ✅ Good:
withFeatureFactory(({ signalA, signalB }: {signalA: A, signalB: B}) => ...)
Here's a reproduction where I explored different approaches to find the most ergonomic and type-safe solution:
https://stackblitz.com/edit/stackblitz-starters-31qrd2nq?file=withFeature%2F1-simple-only-one-helper-to-use.spec.ts
And I made an article about that (https://dev.to/romain_geffrault_10d88369/ngrx-signalstore-hacks-beautiful-dx-with-custom-features-1n4k)
Describe any alternatives/workarounds you're currently using
No response
I would be willing to submit a PR to fix this issue
- Yes
- No