In this article, we'll explore a new feature from the @ngrx/store
package - the createActionGroup
function that is introduced in version 13.2.
Using Action Creator
We usually define action creators using the createAction
function:
// products-page.actions.ts
import { createAction, props } from '@ngrx/store';
// defining an action without payload
export const opened = createAction('[Products Page] Opened');
// defining an action with payload using the `props` function
export const paginationChanged = createAction(
'[Products Page] Pagination Changed',
props<{ page: number; offset: number }>()
);
// defining an action with payload using the props factory
export const queryChanged = createAction(
'[Product Page] Query Changed',
(query: string) => ({ query })
);
In this example, we use the "[Source] Event Name" pattern to define action types where the source of each action is "Products Page". Also, the name of each action creator is equal to the camel-cased name of the event it expresses. For example, the action creator name for the "Pagination Changed" event is "paginationChanged".
💡 If you are not familiar with treating actions as unique events, learn more in this talk by Mike Ryan.
To use products page actions in the products container component, we usually use named import:
// products.component.ts
import * as ProductsPageActions from './products-page.actions';
@Component({ /* ... */ })
export class ProductsComponent implements OnInit {
constructor(private readonly store: Store) {}
ngOnInit(): void {
this.store.dispatch(ProductsPageActions.opened());
}
}
Another common practice is to create a barrel file with named exports from action files:
// products/actions/index.ts
export * as ProductsPageActions from './products-page.actions';
export * as ProductsApiActions from './products-api.actions';
Named exports can be further used in files where needed.
Using Action Group Creator
The createActionGroup
function creates a group of action creators with the same source. It accepts an action group source and an event dictionary as input arguments, where an event is a key-value pair of an event name and event props:
// products-page.actions.ts
import { createActionGroup, emptyProps, props } from '@ngrx/store';
export const ProductsPageActions = createActionGroup({
source: 'Products Page',
events: {
// defining an event without payload using the `emptyProps` function
'Opened': emptyProps(),
// defining an event with payload using the `props` function
'Pagination Changed': props<{ page: number; offset: number }>(),
// defining an event with payload using the props factory
'Query Changed': (query: string) => ({ query }),
},
});
💡 The
emptyProps
function is another addition to the@ngrx/store
package. It is used to define an action without payload within an action group.
The createActionGroup
function returns a dictionary of action creators where the name of each action creator is created by camel casing the event name, and the action type is created using the "[Source] Event Name" pattern:
// action type: [Products Page] Opened
ProductsPageActions.opened();
// action type: [Products Page] Pagination Changed
ProductsPageActions.paginationChanged({ page: 10, offset: 100 });
// action type: [Products Page] Query Changed
ProductsPageActions.queryChanged('ngrx');
Also, there is no longer a need for barrel files or named imports because the action group can be imported directly into another file:
// products.component.ts
import { ProductsPageActions } from './products-page.actions';
@Component({ /* ... */ })
export class ProductsComponent implements OnInit {
constructor(private readonly store: Store) {}
ngOnInit(): void {
this.store.dispatch(ProductsPageActions.opened());
}
}
If we create a new action using the createAction
function by copying the previous one but accidentally forget to change its type, the compilation will pass. Fortunately, this is not the case with the createActionGroup
function - we will get a compilation error if two actions from the same group have the same type.
Limitations
We can define different names for an event and an action creator using the createAction
function:
export const productsLoadedSuccess = createAction(
'[Products API] Products Are Loaded Successfully',
props<{ products: Product[] }>()
);
In this example, the event name is "Products Are Loaded Successfully" and the action creator name is "productsLoadedSuccess". Unfortunately, this is not possible with the createActionGroup
function, because the action creator name will be always equal to the camel-cased event name. So for the event name "Products Are Loaded Successfully", the action creator name will be "productsAreLoadedSuccessfully".
Conclusion
Action group creator improves developer experience by reducing code in action files. It eliminates the need to create barrel files or use named imports for actions, define the same action source in multiple places, and write the same name for the event and the action creator twice. It also enforces good action hygiene by using the "[Source] Event" pattern in defining action types.
Resources
Peer Reviewers
Thank you friends for reviewing the createActionGroup
PR and giving me helpful suggestions on this article!
Top comments (1)
Nice :)
I like the guard for the same action type and the fact that a name is generated based on an action's type.