-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(Store): add action creators #1570
Closed
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import { ActionsUnion, createAction } from '@ngrx/store'; | ||
|
||
describe('createAction with properties', () => { | ||
describe('action type: const', () => { | ||
it('type only', () => { | ||
const ACTION1 = '[action] Action1 blah'; | ||
const actualAction = createAction(ACTION1); | ||
type expectedActionType = Readonly<{ | ||
type: '[action] Action1 blah'; | ||
}>; | ||
|
||
const expectedAction: expectedActionType = { | ||
type: ACTION1, | ||
}; | ||
expect(actualAction).toEqual(expectedAction); | ||
}); | ||
|
||
it('type and properties', () => { | ||
const properties = { userName: 'UserName', age: 32 }; | ||
const ACTION1 = '[action] Action1 blah'; | ||
const actualAction = createAction(ACTION1, properties); | ||
type expectedActionType = Readonly<{ | ||
type: '[action] Action1 blah'; | ||
userName: string; | ||
age: number; | ||
}>; | ||
|
||
const expectedAction: expectedActionType = { | ||
type: ACTION1, | ||
userName: properties.userName, | ||
age: properties.age, | ||
}; | ||
expect(actualAction).toEqual(expectedAction); | ||
}); | ||
}); | ||
|
||
describe('action type: enum', () => { | ||
it('type only', () => { | ||
enum ActionTypes { | ||
Action1 = '[action] Action1 blah', | ||
Action2 = '[action] Action2 blah', | ||
} | ||
|
||
const actualAction = createAction(ActionTypes.Action1); | ||
type expectedActionType = Readonly<{ | ||
type: ActionTypes.Action1; | ||
}>; | ||
|
||
const expectedAction: expectedActionType = { | ||
type: ActionTypes.Action1, | ||
}; | ||
expect(actualAction).toEqual(expectedAction); | ||
}); | ||
|
||
it('type and properties', () => { | ||
const properties = { userName: 'UserName', age: 32 }; | ||
enum ActionTypes { | ||
Action1 = '[action] Action1 blah', | ||
Action2 = '[action] Action2 blah', | ||
} | ||
|
||
const actualAction = createAction(ActionTypes.Action1, properties); | ||
type expectedActionType = Readonly<{ | ||
type: ActionTypes.Action1; | ||
userName: string; | ||
age: number; | ||
}>; | ||
|
||
const expectedAction: expectedActionType = { | ||
type: ActionTypes.Action1, | ||
userName: properties.userName, | ||
age: properties.age, | ||
}; | ||
expect(actualAction).toEqual(expectedAction); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('ActionsUnion type', () => { | ||
enum ActionTypes { | ||
Login = '[Login Page] Login', | ||
Logout = '[Login Page] Logout', | ||
} | ||
|
||
const Actions = { | ||
login: (userName: string, password: string) => | ||
createAction(ActionTypes.Login, { userName, password }), | ||
logout: () => createAction(ActionTypes.Logout), | ||
}; | ||
|
||
type Actions = ActionsUnion<typeof Actions>; | ||
|
||
it('Action with properties', () => { | ||
const properties = { userName: 'UserName', password: 'Password' }; | ||
const actualAction = Actions.login( | ||
properties.userName, | ||
properties.password | ||
); | ||
type expectedActionType = Readonly<{ | ||
type: ActionTypes.Login; | ||
userName: string; | ||
password: string; | ||
}>; | ||
const expectedAction: expectedActionType = { | ||
type: ActionTypes.Login, | ||
userName: properties.userName, | ||
password: properties.password, | ||
}; | ||
expect(actualAction).toEqual(expectedAction); | ||
}); | ||
|
||
it('Action without properties', () => { | ||
const actualAction = Actions.logout(); | ||
type expectedActionType = Readonly<{ | ||
type: ActionTypes.Logout; | ||
}>; | ||
const expectedAction: expectedActionType = { | ||
type: ActionTypes.Logout, | ||
}; | ||
expect(actualAction).toEqual(expectedAction); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// ========== | ||
// = Adapted from: rex-tils | ||
// = https://github.com/Hotell/rex-tils | ||
// ========== | ||
|
||
import { Action, AnyFn } from './models'; | ||
|
||
export function createAction<T extends string>(type: T): Action<T>; | ||
export function createAction< | ||
T extends string, | ||
P extends { | ||
[key: string]: any; | ||
} | ||
>(type: T, props: P): Action<T, P>; | ||
export function createAction< | ||
T extends string, | ||
P extends { | ||
[key: string]: any; | ||
} | ||
>(type: T, props?: P) { | ||
/* | ||
The following line requires Typescript 3.2: Generic spread expressions in | ||
object literals. | ||
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-2.html | ||
|
||
Typescript 3.1.1 gives error: Spread types may only be created from | ||
object types. ts(2698) | ||
*/ | ||
// const action = payload === undefined ?{ type } : { type, ...payload }; | ||
const action = | ||
props === undefined ? { type } : { type, ...(props as object) }; | ||
return action; | ||
} | ||
|
||
/** | ||
* Simple alias to save keystrokes when defining JS typed object maps | ||
*/ | ||
type StringMap<T> = { [key: string]: T }; | ||
export type ActionsUnion<A extends StringMap<AnyFn>> = ReturnType<A[keyof A]>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,16 @@ | ||
import { Action } from '@ngrx/store'; | ||
import { Action, ActionsUnion, createAction } from '@ngrx/store'; | ||
|
||
export enum AuthActionTypes { | ||
Logout = '[Auth] Logout', | ||
LogoutConfirmation = '[Auth] Logout Confirmation', | ||
LogoutConfirmationDismiss = '[Auth] Logout Confirmation Dismiss', | ||
} | ||
|
||
export class Logout implements Action { | ||
readonly type = AuthActionTypes.Logout; | ||
} | ||
|
||
export class LogoutConfirmation implements Action { | ||
readonly type = AuthActionTypes.LogoutConfirmation; | ||
} | ||
|
||
export class LogoutConfirmationDismiss implements Action { | ||
readonly type = AuthActionTypes.LogoutConfirmationDismiss; | ||
} | ||
export const AuthActions = { | ||
logout: () => createAction(AuthActionTypes.Logout), | ||
logoutConfirmation: () => createAction(AuthActionTypes.LogoutConfirmation), | ||
logoutConfirmationDismiss: () => | ||
createAction(AuthActionTypes.LogoutConfirmationDismiss), | ||
}; | ||
|
||
export type AuthActionsUnion = | ||
| Logout | ||
| LogoutConfirmation | ||
| LogoutConfirmationDismiss; | ||
export type AuthActions = ActionsUnion<typeof AuthActions>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,12 @@ | ||
import * as AuthActions from './auth.actions'; | ||
import * as AuthApiActions from './auth-api.actions'; | ||
import * as LoginPageActions from './login-page.actions'; | ||
import { AuthActions, AuthActionTypes } from './auth.actions'; | ||
import { AuthApiActions, AuthApiActionTypes } from './auth-api.actions'; | ||
import { LoginPageActions, LoginPageActionTypes } from './login-page.actions'; | ||
|
||
export { AuthActions, AuthApiActions, LoginPageActions }; | ||
export { | ||
AuthActions, | ||
AuthActionTypes, | ||
AuthApiActions, | ||
AuthApiActionTypes, | ||
LoginPageActions, | ||
LoginPageActionTypes, | ||
}; |
13 changes: 6 additions & 7 deletions
13
projects/example-app/src/app/auth/actions/login-page.actions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,13 @@ | ||
import { Action } from '@ngrx/store'; | ||
import { ActionsUnion, createAction } from '@ngrx/store'; | ||
import { Credentials } from '@example-app/auth/models/user'; | ||
|
||
export enum LoginPageActionTypes { | ||
Login = '[Login Page] Login', | ||
} | ||
|
||
export class Login implements Action { | ||
readonly type = LoginPageActionTypes.Login; | ||
export const LoginPageActions = { | ||
login: (credentials: Credentials) => | ||
createAction(LoginPageActionTypes.Login, { credentials }), | ||
}; | ||
|
||
constructor(public payload: { credentials: Credentials }) {} | ||
} | ||
|
||
export type LoginPageActionsUnion = Login; | ||
export type LoginPageActions = ActionsUnion<typeof LoginPageActions>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think having functions directly in the file would be better. You can then use named module import as a grouping mechanism.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to group them this way to take advantage of the ActionsUnion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So a few issues I have with this:
On the other hand, I don't think anyone at Google is using function-based action creator and this proposal could be fine.
I'm planning to propose something like this for the class-based action creators: https://www.typescriptlang.org/play/index.html#src=%2F%2F%20action_helper.ts%0D%0Ainterface%20Type%3CC%2C%20T%20extends%20string%3E%20%7B%0D%0A%20%20%20new%20(...args%3A%20any%5B%5D)%3A%20C%3B%0D%0A%20%20%20readonly%20type%3A%20T%3B%0D%0A%7D%0D%0Aabstract%20class%20StaticAction%3CT%20extends%20string%3E%20%7B%0D%0A%20%20static%20readonly%20type%3A%20string%3B%0D%0A%20%20readonly%20type!%3A%20T%3B%0D%0A%7D%0D%0A%0D%0Afunction%20Action%3CT%20extends%20string%3E(type%3A%20T)%3A%20Type%3CStaticAction%3CT%3E%2C%20T%3E%20%7B%0D%0A%20%20return%20class%20extends%20StaticAction%3CT%3E%20%7B%0D%0A%20%20%20%20readonly%20type%20%3D%20type%3B%0D%0A%20%20%20%20static%20type%20%3D%20type%3B%0D%0A%20%20%7D%0D%0A%7D%0D%0A%0D%0Aclass%20MyAction%20extends%20Action('foo')%20%7B%7D%0D%0A%0D%0Aconsole.log(new%20MyAction().type)%3B%20%2F%2F%20logs%3A%20'foo'%0D%0Aconsole.log(MyAction.type)%3B%20%2F%2F%20logs%3A%20'foo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should add that this was @kolodny idea, that I iterated on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will you open a new issue with this proposal? I'd lean towards creating a new API such as
withAction('type')
for this as opposed to changingAction
.