NgRx 8 — Meet the new, upcoming factory methods of the next major release
NgRx 8 has introduced new possibilities to create actions, reducers and effects. This article will summarize the new APIs that are available. 🎊
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
andcreateEffect
.
@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. 🎊
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.
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.
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