Let's have a chat about Actions and Action Creators within NgRx
I think it’s time you and I talk a bit about action creators. But before we get into action creators let’s first start with actions, define what they are and why they are needed.
In a NgRx (and Redux) application one of the first rules you learn is that the store state is read only. But how does one modify the state, you may ask. The answer to that question is through actions and reducers.
In order to update to the store state we have to send messages from the application to the store. These messages are called actions, you can compare an action as a transaction record. It represents something that took place in the application and can be considered a statement of fact. The reducers in the store are eagerly waiting for an action to pass through so it can create the new store state.
Update 2019–04–05 link
NgRx introduces Action Creators in version 7.4.0, allowing us to create a typed action by using createAction
.
To create the action creator, we define the action’s type as the first parameter and use the second parameter to define additional action properties. We don’t need to define an action’s enum as this is accessible via the type property, for example orderFood.type
. For more info about this change see Alex Okrushko's post NgRx: Action creators redesigned.
Actions link
An action is a Plain Old JavaScript Object (POJO), it defines its intention with the type
property and it has an optional payload. In fact you can define the shape of the action yourself, the only enforcement is that it must have a type
property and its value must be a string. Other than that the structure of the action can be shaped to your needs, although, I would encourage you to use a payload
property to add extra data to the action.
With these type values Redux Devtools provides an event log of everything that has happened inside your application.
An example of an action with and without payload looks as follows:
In a typical NgRx application you will probably see these action types defined as enums
. Doing this has the benefit to typecheck the action types, this is done because a typo is always one keystroke away and can be the cause of frustration for several minutes.
The refactored version of the actions above becomes:
format_quoteSIDE NOTE: Instead of using an ActionTypes
enum
it’s also a possibility to use constants to define the actions, as inconst ORDER_FOOD = ‘[Order Page] ORDER FOOD’
. The behavior will be the same, so feel free to use your preferred way.
Now that we know what an action is, we have to send it to the store. To do this we use the dispatch
function which exists on the store.
Dispatching an action like this may seem OK at first sight but has the downside that you have to type out the action every time. You have to define the type, you have to import the ActionType
enum, and you have to define the payload. Speaking of the payload, you have to remember the structure of the payload and if it changes in the future you’ll have to modify it throughout your whole codebase.
Here is where action creators come into play.
Action creators link
Like the name already gives away, an action creator creates an action. This can be done in multiple ways, in this post we’ll cover the two most frequently used ones.
Creating an action via a class link
The class has a readonly
type property and via its constructor it gets the payload of the action, which can (and must) be typed.
If we just create the action object from the constructor’s input, we can shorten this as:
Notice that the payload is set as a readonly property in the constructor.
In order to dispatch the action we have to create a new instance of the class:
Creating an action via a factory function link
The function returns the action object based on the function’s input and sets the type.
To dispatch the action we call the function:
Personally, while this may not be the de facto way, this is my preferred way. Because using a factory function prevents common or easily made mistakes, especially if you’re new to NgRx or Redux in general.
One of these mistakes is that the action must be serializable. While a factory function doesn’t fully prevent this, it’s preventing some constructs which can be created or are even encouraged with classes.
Benefits of using Action Creators link
If you think this is the only reason to use action creators, well… you’re wrong. An action creator also has some extra benefits.
The view layers of the application don’t need to know about action types, these are encapsulated inside the action creators. The only places where you would use action types are in the action creators (to create the action object), inside the reducer functions (to know when and how to modify the state) and inside the effects (to invoke side effects).
Unlike reducers, an action creator doesn’t need to be pure. The inside of an action creator is the place where you could add impure logic, e.g. adding the current time to the action with Date.now()
, generating a UUID, etc. A small tip that I would give here, is to make these properties overridable which would help you out when you’re writing tests, for example:
Which is used in the application as follows:
And in the tests where you need more control, you can override these values:
Inside action creators you can make calculations on the input instead of just returning the action object.
An action creator helps you and your team to remember the data which is needed to create an action via its input. This input can be transformed into the payload property of the action.
Creating the “right” payload structure has the advantage of tightening up your reducer. If we play it smart we can handle several actions at once by using the fall-through functionality of a switch statement:
In comparison to:
You only need to write the action creator unit test(s) once, in contrast to testing the action in every component where it would be used. A unit test can be as simple as:
Conclusion link
By adding a small (but well known) abstraction layer into the NgRx flow, we end up with cleaner code suited for reusability. With the result of making it ourselves and our team easier to scale and maintain an application.
This is because:
- We remove boilerplate, although this may not seem like it
- We create a place for impure logic and calculations
- We make our tests less prone to break due to changes
A big thank you to Nicholas Jamieson and Alex Okrushko for taking the time to review this post! 🙌
Incoming links
- Common and easy-to-make mistakes when you’re new to NgRx
- Keeping browser tabs in sync using localStorage, NgRx, and RxJS
Feel free to update this blog post on GitHub, thanks in advance!
Join My Newsletter (WIP)
Join my weekly newsletter to receive my latest blog posts and bits, directly in your inbox.
Support me
I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.