I have a NgRX signal store like so:
type ResultType = any;
export const MyCollectionsStore = signalStore(
withState<{
loading: boolean;
loadingError: string | null;
result: ResultType | null;
}>({
loading: false,
loadingError: null,
result: null,
}),
withMethods((store, collectionsApiService = inject(CollectionsService)) => ({
loadPage: rxMethod<{ page: number; itemsPerPage: number }>(
pipe(
tap(() => patchState(store, { loading: true, loadingError: null })),
switchMap(({ page, itemsPerPage }) => {
return collectionsApiService
.getMyCollections(page, itemsPerPage)
.pipe(
tapResponse({
next: (result) => patchState(store, { result }),
error: (e) => patchState(store, { loadingError: `${e}` }),
finalize: () => patchState(store, { loading: false }),
}),
);
}),
),
),
})),
);
I would like to extract the withState
and withMethods
calls in a separate signalStoreFeature
, but I want it to accept a name, like the named entity collections of the withEntities
feature.
I figured out the state, but I’m still struggling on the methods part:
export type StatefulMethodResultState<TResult extends object> = {
loading: boolean;
loadingError: string | null;
result: TResult | null;
};
export type NamedStatefulMethodResultState<
TResult extends object,
TName extends string,
> = {
[K in TName as `${K}State`]: StatefulMethodResultState<TResult>;
};
export const withStatefulMethodResult = <
TResult extends object,
TName extends string,
>(
method: (...params: any) => any,
name: TName,
) => {
return signalStoreFeature(
withState<NamedStatefulMethodResultState<TResult, TName>>({
[`${name}State`]: {
loading: false,
loadingError: null,
result: null,
},
} as NamedStatefulMethodResultState<TResult, TName>),
withMethods((store) => ({
[name]: () => method(store),
})),
);
};
I’m not sure what type I need to specify on the method
parameter. Also I obviously would like to be able to inject stuff in the method
when I declare it, so the consuming code would look something like this:
withStatefulMethodResult(
(
store,
collectionsApiService = inject(CollectionsService),
) =>
pipe(
debounceTime(200),
tap(() => patchState(store, { loadPageState: { loading: true, loadingError: null } })),
switchMap(({ page, itemsPerPage }) => {
return collectionsApiService.getMyCollections(page, itemsPerPage).pipe(
tapResponse({
next: (result) => patchState(store, { loadPageState: { result } }),
error: (e) => patchState(store, { loadingError: `${e}` }),
finalize: () => patchState(store, { loadPageState: { loading: false } }),
}),
);
}),
),
'loadPage',
);
Ideally I would like an answer for NgRX, but if NgRX isn’t the right tool for this job I’m open to other suggestions.