NgRx Action Group Creator
Tags: ngrx, angular
Published: 2022-05-03 11:56:55

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:

```ts
// 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](https://youtu.be/JmnsEvoy-gY) by [Mike Ryan](https://twitter.com/MikeRyanDev).

To use products page actions in the products container component, we usually use named import:

```ts
// 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:

```ts
// 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:

```ts
// 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:

```ts
// 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:

```ts
// 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:

```ts
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

- [Official docs of the `createActionGroup` function](https://ngrx.io/api/store/createActionGroup)

## Peer Reviewers

- [Brandon Roberts](https://twitter.com/brandontroberts)
- [Tim Deschryver](https://twitter.com/tim_deschryver)

Thank you friends for reviewing the `createActionGroup` PR and giving me helpful suggestions on this article!