NgRx 8 — Meet the new, upcoming factory methods of the next major release

The upcoming major release of NgRx will bring new possibilities to create actions, reducers and effects. This article will summarize the new APIs that will be available soon. 🎊

Show me the code

You can check out a full working demo with the new creator-methods hosted on ⚡️ StackBlitz.

Action Creators — createAction

Plain Action

There will be a new way of creation actions in NgRx. Instead of creating a class for each action the method createAction can be used.

// Before
export class LogInfo {
readonly type = '[Logger] Info'
}
// After
export const logInfo = createAction('[Logger] Info');

Action with payload

For specifying the payload createAction accepts a second parameter expecting a method called props<T>. The generic parameter T allows specifying the type of the payload of an action.

export const logInfo = createAction(
'[Logger] Info',
props<{payload: { message: string }}>()
);

Action having default values

It is also possible to define default parameters by turning props into an arrow-function.

export const login = createAction(
'[Logger] Info',
(message = 'Dispatching an action') => ({ payload: { message }})
);

The createAction-factory saves some lines of code. The type-information provided by createAction allowing to automatically infer the payload type of the respective action. There is no need anymore to tell the TypeScript compiler the which type of action is going to be processed. 👍

Discussion

Alex Okrushko wrote an excellent article about the new action creators. If you want to learn more about createAction this article is definitely worth a read.

The following sentence is a quote from the article mentioned above.

With the new Action Creator, payload could become a thing of the past — a
props() function takes care of adding properties without payload!

Since Redux exists it is totally up to you how an action is structured. Nevertheless, it is recommended to stick with the simple contract that actions contain a type- and an optional payload-property. However, props() will allow generating actions without payload-property.

💁‍♂️ Just be a bit careful leaving out the payload since an existing codebase could rely on the contract that the data that should be transformed by reducers- or meta-reducers is transported within the payload-property.

Get rid of switch-case-statements - createReducer

There are different strategies for reducing Boilerplate in Redux. One idea is to get rid of the infrastructural code that is introduced by switch-case-statements.

📖 Some very useful refactoring methods can be found at redux.js.org.

In NgRx 8 createReducer will allow to set up the binding between an action and the logic (also called case-reducer) that should be executed since this action has been dispatched.

const initialState: CounterState = { count: 0 };
export const reducer = createReducer(
initialState,
on(add, (state, { payload }) => ({
...state,
count: state.count + payload.value
}))
);

The first parameter takes the default state. After it, any number of mutation methods can be added wrapped in a helper function called on that brings together the action with the respective case-reducer.

At first, the action (in this case add) is passed to on. Second, a method needs to be passed taking the existing state and the respective action to produce a new state.

The following switch-case-statement would behave the same as the snippet above.

export const function counterReducer(
state = initialState,
action: CounterActions
) {
switch (action.type) {
case(Add): return {
...state,
count: state.count + action.payload.value
},
default: return state
}
}

Comparing both versions shows that createReducer does not need a union type to infer the payload of an action. Furthermore, the default case of the switch-case-statement drops out since the reducer factory already takes care of it.

Ahead of time compilation (AoT)

In my opinion, using the createReducer will make the code more readable since it describes the flow of an Action better than before.

Nevertheless, we need to take care of some technical restrictions coming with AoT-Compilation. export const counterReducer = createReducer( ... ) will break the production build. This is because AoT needs to be able statically to analyze each code branch being executed due to compilation. Let’s make createReducer compatible with AoT by wrapping it into an exported, named function.

const initialState: CounterState = { count: 0 };
export const reducer = createReducer(
initialState,
/* ... */
);
export function counterReducer(
state: CounterState | undefined,
action: Action) {
return reducer(state, action);
}

Now the code also compiles using AoT-compilation. 👏👏

Handle side effects — createEffect

Last but not least NgRx 8 will bring an alternative way to create side effects. Instead of using the decorator @Effect the method createEffect can be used.

🛣 As far as I know, no breaking change is planned in NgRx 8 concerning effects. You will be able to use both @Effect and createEffect.
@Injectable()
export class CounterEffects {
  // Before
@Effect({ dispatch: false })
logAdd = this.actions.pipe(
ofType(add),
// ...
)
  // After
logAdd = createEffect(() => this.actions.pipe(
ofType(add),
// ...
), { dispatch: false })
  constructor(private actions: Actions) { }
}

The new method simply takes a function that contains the logic handling the action-stream. If an effect should not dispatch an action to the store a second parameter can be passed where the effect can be configured accordingly.

Type-inference

NgRx 7 already introduced a better type-inference for actions. Using createAction will provide the same comfort. In the following screenshot, the action add is passed to the operator ofType. Afterwards, the action is destructured in the tap operator because only the payload-property is needed. The TypeScript-Compiler can infer the type of the payload automatically. 🎊

Processing an Action generated with createAction

Putting it all together

The new factory-methods apply useful refactoring techniques to NgRx 8. They reduce boilerplate which I believe will be very appreciated by the community. 🤗

Refactoring is an ongoing process

The NgRx team also puts a lot of effort into the documentation. If you are interested in the other cool features coming up you can check out Documentation to read about @ngrx/data. There will be a few breaking changes as well but they are already documented in the migration guide.

I also questioned how the coding experience could be improved. I ended up developing a library that also makes use of the refactoring techniques mentioned in this article. Furthermore, it provides a dynamic, typed facade that simplifies the use of the Store in container components.

NgRx Ducks | https://github.com/co-IT/ngrx-ducks

The library is called NgRx Ducks. I also wrote a few articles about it (You will find the links below this article). If you are interested in writing less code and less maintenance work I kindly invite you to give the Ducks-Pattern a try.

Recapitulation

  • Actions can be created with createAction.
  • Treat the payload-property carefully
  • Reducers can be created with createReducer.
  • Effects can be created with createEffects.
  • The type-information of an Action can automatically be inferred by the TypeScript-Compiler
  • The new create*-methods reduce boilerplate and improve type-safety.

That’s it ✨

Thank you for reading this article. Since NgRx 8 is still in BETA I will update this article if anything changes. 💁‍♂️

If you have any further questions please leave me a comment below.
You also can write me a message on Twitter if you like: @GregOnNet

Rock On And Code
Gregor

Related articles