I'm trying to create a generic function that will create some pretty common effects, so I'll be able to pass only the config and it will create the effect.
Here are some imports from NgRx:
import { ActionCreator } from '@ngrx/store';
import { createActionGroup, props } from '@ngrx/store';
and one ActionGroup for testing purposes:
export const featureActions = createActionGroup({
source: 'MyGroup',
events: {
'Update Entity': props<{ data: { updateName: string } }>(),
'Delete Entity': props<{ data: { deleteName: string } }>(),
},
});
So to achieve that I've created a config file that accepts a ngrx action (ActionCreator) and one mapping function.
export type Config<AC extends ActionCreator> = {
action: AC;
mapping: (data: ReturnType<AC>) => string;
};
The factory function itself accepts the array of these configs:
export function createMyEffects<AC extends ActionCreator>(
effects: Config<AC>[]
) {
// doing nothing, I guess it is not relevant
console.log(effects);
}
The issue happens on function call:
createMyEffects([
{
action: featureActions.updateEntity,
mapping: (updateData) => updateData.data.updateName, // place #2
},
{
// Issue is that it thinks that this action should be the same as the first
action: featureActions.deleteEntity, // the place #1
mapping: (deleteData) => deleteData.data.deleteName, // place #3
},
]);
So with my function declaration TS throws me at place #1 that typeof FeatureAtions.deleteEntity
isn't assignable to typeof FeatureAtions.updateEntity
.
So I changed it to
export function createMyEffects<AC extends ActionCreator[]>(
effects: Config<AC[number]>[],
)
and in this case, there are no errors, but now I'm losing the type inference at places #2 and #3. deleteData
and updateData
args infer type Object
instead of ReturnType< FeatureAtions.deleteEntity>
and ReturnType<FeatureAtions.updateEntity>
corresponding.
How could I ensure the type inference in this case?
UPD: Here is stackblits link with env
If you want each element of your effects
array input to have its own type argument to Config<>
, then you will need to make the function generic in a tuple type of those type arguments, and make effects
a mapped tuple type:
function createMyEffects<AC extends ActionCreator[]>(
effects: { [I in keyof AC]: Config<AC[I]> }
) {
console.log(effects);
}
This will then work as desired, at least for TypeScript 5.4 and above:
createMyEffects([
{
action: featureActions.updateEntity,
mapping: (updateData) => updateData.data.updateName,
},
{
action: featureActions.deleteEntity,
mapping: (deleteData) => deleteData.data.deleteName,
},
]);
Unfortunately before TypeScript 5.4 this wouldn't work properly... TypeScript has the ability to infer from mapped types, but when the function input is an array literal this didn't work right. It was fixed in microsoft/TypeScript#56555.