DEV Community

Cover image for NgRx Action Group Creator
Marko Stanimirović for NgRx

Posted on • Edited on

NgRx Action Group Creator

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 })
);
Enter fullscreen mode Exit fullscreen mode

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());
  }
}
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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 }),
  },
});
Enter fullscreen mode Exit fullscreen mode

💡 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');
Enter fullscreen mode Exit fullscreen mode

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());
  }
}
Enter fullscreen mode Exit fullscreen mode

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[] }>()
);
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
wojtrawi profile image
Wojciech Trawiński

Nice :)
I like the guard for the same action type and the fact that a name is generated based on an action's type.