-
-
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
I have found I often have reusable signal store features that I want to use multiple times in the same signal store or in other features with property name constraints. Having the ability to pull in all the state, methods, properties, etc.. for a given feature under some unique key defined where you use the feature would be useful.
Something like the following is what I am thinking as far as api
const withCommonFeature = () =>
signalStoreFeature(
withState(() => ({ commonField: 'Hello world!' })),
withMethods((store) => ({
setField: (value: string) => {
patchState(store, setField(value));
},
}))
);
const Store = signalStore(
withKeyed('A', withCommonFeature()),
withKeyed('B', withCommonFeature()),
withMethods((store) => ({
log() {
console.log(store['A'].commonField());
console.log(store['B'].commonField());
},
}))
);
withKeyed
would be the function defined in the standard library in this example.
I have a somewhat working fork, if this feature is considered.
Note it is still a little rough around utilising writable state so any ideas on that could be discussed here. The main con in my current pass is I have a watchState because I am not running the keyed feature on the root store , so two stateSources need to be kept in sync when patches happen inside the inner and outer stores.
Describe any alternatives/workarounds you're currently using
The project I am working in is still on v18, so my workaround is for that version of signal store (i can port this to v19 for those interested if need be). Its a rough version using native withComputed as shown here:
type ComputedSignalsKeyType<O extends SignalStoreFeatureResult> = keyof O['computed'];
type ComputedSignalsValueType<O extends SignalStoreFeatureResult> = {
[x in ComputedSignalsKeyType<O>]: O['computed'][x] extends Signal<infer V> ? V : O['computed'][x];
};
type WithKeyedOutputComputedResult<O extends SignalStoreFeatureResult> = O['computed'] &
Signal<O['state'] & ComputedSignalsValueType<O>> &
O['methods'];
type WithKeyedOutputStateResult<O extends SignalStoreFeatureResult> = O['state'] & ComputedSignalsValueType<O>;
type WithKeyedOutputResult<T extends string | symbol, O extends SignalStoreFeatureResult> = EmptyFeatureResult & {
state: { [x in T]: WithKeyedOutputStateResult<O> };
computed: {
[x in T]: WithKeyedOutputComputedResult<O>;
};
methods: {};
};
export function withKeyed<
T extends string | symbol,
I extends SignalStoreFeatureResult,
O extends SignalStoreFeatureResult,
>(key: T, innerFeature: SignalStoreFeature<I, O>): SignalStoreFeature<I, WithKeyedOutputResult<T, O>> {
return (store) => {
const storeForFactory = inject(
signalStore(
{ providedIn: 'root' },
withState(() => ({}))
)
) as Parameters<SignalStoreFeature<I, O>>[0];
const storeForFactoryWithHooks = {
...storeForFactory,
hooks: (store as any).hooks ?? {},
};
const _innerStore = innerFeature(storeForFactoryWithHooks);
const computedValues = computed(() => {
const computedSignals = _innerStore.computedSignals;
const computedKeys: ComputedSignalsKeyType<O>[] = Object.keys(computedSignals);
return computedKeys.reduce((acc, k) => {
acc[k] = isSignal(computedSignals[k]) ? computedSignals[k]() : (computedSignals[k] as any);
return acc;
}, {} as ComputedSignalsValueType<O>);
});
const state = computed(() => {
return { ...getState(_innerStore), ...computedValues() };
});
Object.assign(state, _innerStore.methods);
Object.assign(state, _innerStore.computedSignals);
Object.assign(state, _innerStore.stateSignals);
const storeWithKey = withComputed(
() =>
({
[key]: state,
}) as {
[x in T]: WithKeyedOutputComputedResult<O>;
}
)(store);
const storeWithHooks = withHooks(() => _innerStore.hooks)(storeWithKey) as any;
return storeWithHooks;
};
}
Without some of the internal symbols, types, and helper methods, its a little verbose and limited on how writes inside the keyed feature are handled. Especially when their are keyed features inside other keyed features, thus the feature request.
I would be willing to submit a PR to fix this issue
- Yes
- No