Skip to content
Merged
15 changes: 15 additions & 0 deletions libs/ngrx-toolkit/src/lib/with-call-state.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,19 @@ describe('withCallState', () => {
expect(dataStore.entitiesCallState()).toBe('loaded');
expect(dataStore.entitiesLoaded()).toBe(true);
});

it('should use the callState for multiple collections with an array', () => {
const DataStore = signalStore(
{ protectedState: false },
withCallState({ collections: ['entities', 'products'] })
);
const dataStore = new DataStore();

patchState(dataStore, setLoaded('entities'), setLoaded('products'));

expect(dataStore.entitiesCallState()).toBe('loaded');
expect(dataStore.productsCallState()).toBe('loaded');
expect(dataStore.entitiesLoaded()).toBe(true);
expect(dataStore.productsLoaded()).toBe(true);
});
});
84 changes: 69 additions & 15 deletions libs/ngrx-toolkit/src/lib/with-call-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export type CallStateSlice = {
};

export type NamedCallStateSlice<Collection extends string> = {
[K in keyof CallStateSlice as `${Collection}${Capitalize<K>}`]: CallStateSlice[K];
[K in keyof CallStateSlice as Collection extends '' ? `${Collection}${K}` : `${Collection}${Capitalize<K>}`]: CallStateSlice[K];
};

export type CallStateSignals = {
Expand All @@ -24,23 +24,44 @@ export type CallStateSignals = {
};

export type NamedCallStateSignals<Prop extends string> = {
[K in keyof CallStateSignals as `${Prop}${Capitalize<K>}`]: CallStateSignals[K];
[K in keyof CallStateSignals as Prop extends '' ? `${Prop}${K}` : `${Prop}${Capitalize<K>}`]: CallStateSignals[K];
};

export type SetCallState<Prop extends string | undefined> = Prop extends string
? NamedCallStateSlice<Prop>
: CallStateSlice;

export function getCallStateKeys(config?: { collection?: string }) {
const prop = config?.collection;
export function deriveCallStateKeys<Collection extends string>(collection?: Collection) {
return {
callStateKey: prop ? `${config.collection}CallState` : 'callState',
loadingKey: prop ? `${config.collection}Loading` : 'loading',
loadedKey: prop ? `${config.collection}Loaded` : 'loaded',
errorKey: prop ? `${config.collection}Error` : 'error',
callStateKey: collection ? `${collection}CallState` : 'callState',
loadingKey: collection ? `${collection}Loading` : 'loading',
loadedKey: collection ? `${collection}Loaded` : 'loaded',
errorKey: collection ? `${collection}Error` : 'error',
};
}

export function getCallStateKeys(config?: { collection?: string}) {
const prop = config?.collection;
return deriveCallStateKeys(prop);
}

export function getCollectionArray(config: { collection?: string } | { collections?: string[] }){
return 'collections' in config
? config.collections
: 'collection' in config && config.collection
? [config.collection]
: undefined;
}

export function withCallState<Collection extends string>(config: {
collections: Collection[];
}): SignalStoreFeature<
EmptyFeatureResult,
EmptyFeatureResult & {
state: NamedCallStateSlice<Collection>;
props: NamedCallStateSignals<Collection>;
}
>;
export function withCallState<Collection extends string>(config: {
collection: Collection;
}): SignalStoreFeature<
Expand All @@ -56,18 +77,51 @@ export function withCallState(): SignalStoreFeature<
state: CallStateSlice;
props: CallStateSignals;
}
>;
export function withCallState<Collection extends string>(config?: {
>;export function withCallState<Collection extends string>(config?: {
collection: Collection;
} | {
collections: Collection[];
}): SignalStoreFeature {
const { callStateKey, errorKey, loadedKey, loadingKey } =
getCallStateKeys(config);

return signalStoreFeature(
withState({ [callStateKey]: 'init' }),
withState(() => {
if (!config) {
return { callState: 'init' };
}
const collections = getCollectionArray(config);
if (collections) {
return collections.reduce(
(acc, cur) => ({
...acc,
...{ [cur ? `${cur}CallState` : 'callState']: 'init' },
}),
{}
);
}

return { callState: 'init' };
}),
withComputed((state: Record<string, Signal<unknown>>) => {
if (config) {
const collections = getCollectionArray(config);
if (collections) {
return collections.reduce<Record<string, Signal<unknown>>>((acc, cur: string) => {
const { callStateKey, errorKey, loadedKey, loadingKey } =
deriveCallStateKeys(cur);
const callState = state[callStateKey] as Signal<CallState>;
return {
...acc,
[loadingKey]: computed(() => callState() === 'loading'),
[loadedKey]: computed(() => callState() === 'loaded'),
[errorKey]: computed(() => {
const v = callState();
return typeof v === 'object' ? v.error : null;
}),
};
}, {});
}
}
const { callStateKey, errorKey, loadedKey, loadingKey } = deriveCallStateKeys();
const callState = state[callStateKey] as Signal<CallState>;

return {
[loadingKey]: computed(() => callState() === 'loading'),
[loadedKey]: computed(() => callState() === 'loaded'),
Expand Down