diff --git a/modules/store/spec/action_creator.spec.ts b/modules/store/spec/action_creator.spec.ts index 802fffa755..bf95e8087b 100644 --- a/modules/store/spec/action_creator.spec.ts +++ b/modules/store/spec/action_creator.spec.ts @@ -77,6 +77,13 @@ describe('Action Creators', () => { const value = fooAction.bar; `).toFail(/'bar' does not exist on type/); }); + it('should not allow type property', () => { + expectSnippet(` + const foo = createAction('FOO', (type: string) => ({type})); + `).toFail( + /Type '{ type: string; }' is not assignable to type '"type property is not allowed in action creators"/ + ); + }); }); describe('empty', () => { @@ -141,5 +148,14 @@ describe('Action Creators', () => { const value = fooAction.bar; `).toFail(/'bar' does not exist on type/); }); + + it('should not allow type property', () => { + const foo = createAction('FOO', props<{ type: number }>() as any); + expectSnippet(` + const foo = createAction('FOO', props<{ type: number }>()); + `).toFail( + /Argument of type '"type property is not allowed in action creators"' is not assignable to parameter of type/ + ); + }); }); }); diff --git a/modules/store/src/action_creator.ts b/modules/store/src/action_creator.ts index 117a2aff59..79d0851285 100644 --- a/modules/store/src/action_creator.ts +++ b/modules/store/src/action_creator.ts @@ -3,13 +3,13 @@ import { ActionCreator, TypedAction, FunctionWithParametersType, - ParametersType, + PropsReturnType, + DisallowTypeProperty, } from './models'; -/** - * Action creators taken from ts-action library and modified a bit to better - * fit current NgRx usage. Thank you Nicholas Jamieson (@cartant). - */ +// Action creators taken from ts-action library and modified a bit to better +// fit current NgRx usage. Thank you Nicholas Jamieson (@cartant). + export function createAction( type: T ): ActionCreator TypedAction>; @@ -17,20 +17,20 @@ export function createAction( type: T, config: { _as: 'props'; _p: P } ): ActionCreator P & TypedAction>; -export function createAction( +export function createAction< + T extends string, + P extends any[], + R extends object +>( type: T, - creator: C -): FunctionWithParametersType< - ParametersType, - ReturnType & TypedAction -> & - TypedAction; -export function createAction( + creator: Creator> +): FunctionWithParametersType> & TypedAction; +export function createAction( type: T, - config?: { _as: 'props' } | Creator + config?: { _as: 'props'; _p: object } | C ): Creator { if (typeof config === 'function') { - return defineType(type, (...args: unknown[]) => ({ + return defineType(type, (...args: any[]) => ({ ...config(...args), type, })); @@ -40,8 +40,8 @@ export function createAction( case 'empty': return defineType(type, () => ({ type })); case 'props': - return defineType(type, (props: unknown) => ({ - ...(props as object), + return defineType(type, (props: object) => ({ + ...props, type, })); default: @@ -49,8 +49,10 @@ export function createAction( } } -export function props

(): { _as: 'props'; _p: P } { - return { _as: 'props', _p: undefined! }; +export function props

(): PropsReturnType

{ + // the return type does not match TypePropertyIsNotAllowed, so double casting + // is used. + return ({ _as: 'props', _p: undefined! } as unknown) as PropsReturnType

; } export function union< diff --git a/modules/store/src/models.ts b/modules/store/src/models.ts index ccd5deeebd..9c43b56b58 100644 --- a/modules/store/src/models.ts +++ b/modules/store/src/models.ts @@ -49,7 +49,24 @@ export type SelectorWithProps = ( props: Props ) => Result; -export type Creator = (...args: any[]) => object; +export type DisallowTypeProperty = T extends { type: any } + ? TypePropertyIsNotAllowed + : T; + +export const typePropertyIsNotAllowedMsg = + 'type property is not allowed in action creators'; +type TypePropertyIsNotAllowed = typeof typePropertyIsNotAllowedMsg; + +export type Creator< + P extends any[] = any[], + R extends object = object +> = R extends { type: any } + ? TypePropertyIsNotAllowed + : FunctionWithParametersType; + +export type PropsReturnType = T extends { type: any } + ? TypePropertyIsNotAllowed + : { _as: 'props'; _p: T }; export type ActionCreator< T extends string = string,