Ngrx: How to simplify the definition of actions?

Issue

Is there any way to simplify the definition of actions so that we don’t need to repeat code over and over again?

Problem

Working with Ngrx we have to work with actions all the time. In my situation, the most time each action does have a "Do"-Action, a "Success"-Action and a "Failure"-Action. In my situation each of these actions does have the same scope (e.g. "[Authentication]") and the same label (e.g. "Login user").

When I want to create actions for user authentication I would create these as follows:

export const loginUser = createAction(
    '[Authentication] DO: Login user',
    props<{
      username: string;
      password: string;
    }
);

export const loginUserSuccess = createAction(
    '[Authentication] SUCCESS: Login user',
    props<{
      token: string;
    }
);

export const loginUserFailure = createAction(
    '[Authentication] FAILURE: Login user',
    props<{
      error: string;
    }
);

As you can see there is a lot of repetition:

  • three "[Authentication]" for the same scope
  • three "Login user" for the same kind of action
  • the "DO", "SUCCESS", "FAILURE" parts are the same in all actions

Is there any way to create a factory that simplifies the action creation with less redundant code?

My current solution

See my answer to this post.

I created a package ngrx-simple with the goal of simplifying ngrx development. It implements a class SimpleActions that helps to group actions and reduce code repetition:

https://github.com/julianpoemp/ngrx-simple


My old solution

(the code in the new package is better)

The only simplification I found so far is creating a class of actions of the same scope and wrap all actions for the same kind of action in an object:

store.functions.ts

import {createAction, props} from '@ngrx/store';

export function createDoActionType(scope: string, label: string) {
  return `[${scope}] DO: ${label}`;
}

export function createSuccessActionType(scope: string, label: string) {
  return `[${scope}] SUCCESS: ${label}`;
}

export function createErrorAction(scope: string, label: string) {
  return createAction(
    `[${scope}] FAILURE: ${label}`,
    props<ErrorProps>()
  );
}

export interface ErrorProps {
  error: string;
}

authentication.actions.ts

export class AuthenticationActions {
  private static scope = 'Authentication';

    static loginUser = ((scope: string, label: string) => ({
    do: createAction(
      createDoActionType(scope, label),
      props<{
        userEmail: string;
        password: string;
      }>()
    ),
    success: createAction(
      createSuccessActionType(scope, label),
      props<{
        userID: number;
        token: string;
      }>()
    ),
    failure: createErrorAction(scope, label)
  }))(AuthenticationActions.scope, 'Login user');
}

I see my solution as workaround only. Although it saves me 10 lines of code, it isn’t optimal…

Solution

Ngrx offers a function called createActionGroup. It allows to create a set of actions with the same context ("called source).

The following example creates a set of actions for a group called "click":

import {createActionGroup} from "@ngrx/store";

export const click = createActionGroup({
  source: 'button/click',
  events: {
      do: props<TestProps>(),
      success: emptyProps(),
      fail: props<TestProps>()
  }
});

Now we can dispatch the actions as common:

this.store.dispatch(click.do({test: "ok"});
this.store.dispatch(click.success());
this.store.dispatch(click.fail({test: "ok"});

This dispatches actions with following types:

[button/click] do
[button/click] success
[button/click] fail

In my case there is still need for improvements like a method that creates only the actions "do, success, fail" without the need to set props (no props = emptyProps() automatically). But this is good for now.

Answered By – julianpoemp

Answer Checked By – Willingham (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.