NgRx framework for modern Angular frontend architectures
As a frontend developer, you will often encounter the NgRx framework in modern Angular frontend architectures. If not, it is worth looking if you are building a complex web application.
Imagine being almighty powerful and always knowing the state of your application. As an application grows in complexity, knowing the state of your app will come in handy.
But first things first, let’s begin by explaining some key concepts of a regular end to end flow that will help us familiarize ourselves with some deeper code examples going forward.
What is NgRx ?
NgRx is a framework for building reactive applications in Angular. It is inspired by the Redux pattern, unifying the events in your application, and deriving state using RxJS (but we can discuss the perks of using RxJS on another occasion, it deserves its own article). At a high level, NgRx stores a single state and uses actions to express state changes are about to happen.
How does NgRx work?
NgRx is made up by 5 main key concepts that we will discuss further ahead:
· Store
· Actions
· Reducers
· Effects
· Selectors
Store
The Store in NgRx acts as the application’s single source of truth and it reflects the current state of the app.
Actions
Actions express unique events that happen in our application. These events range from application lifecycle events and user interactions to network requests. Basically, any action you can think of could be dispatched (triggered) in your application. In other words, actions are how the application communicates with NgRx to tell it what to do.
You are never going to change the state directly, instead you are going to dispatch actions. These actions describe what’s happening in your application. Some examples could be getting, adding, removing or updating the state, among others.
Reducers
Reducers are responsible for handling transitions between states. Reducers react to the actions dispatched and execute a pure function to update the Store. Pure functions are functions that are predictable and have no side effects.
Reducers make a copy of the state, modify the current state, and return a new updated version of the state.
Effects
As one colleague once told me: ‘Effects are the glue code that holds everything together in our application’, they can also handle the side effects of each action.
In many cases, an effect gets triggered by dispatching an action because some side effects are going to happen before calling the reducer. This can be something like calling an http service to get data. After the effect is done and side effects are finished, a new action gets fired by the effect.
For example, a side effect could be if an http call succeeded or failed. We will need to set the result into the state by dispatching another action depending on if our call to the service was successful or it failed.
Now the Store has a new updated state. This state can be a big object tree, so NgRx introduces selectors to be able to use only the slices of the object that we need in a specific component.
Selectors
Selectors are the way on how our application can listen to state changes (they are always listening 👀). They are also pure functions for getting slices of the state from the Store, and we can also combine many selectors and apply some logic to deduce a new slice of information from the Store.
NgRx example
Now that we have been through a quick explanation on the key concepts of NgRx, let’s continue with an example. This could easily be another article, but it would make no sense to me having explained all the key concepts without sharing a code example where we can see everything in action ♥.
Folder structure of the Store
Let’s begin by creating the folder structure with the five elements of our store:
Creating State and initial values
For this example, we will get the profile information from a user, so let’s define the initial state values and their interface, e.g. user.state.ts
:
Creating Actions
Actions are where pretty much every flow starts. Following the example of a user, we will get the profile information alongside its possible side effects (success or fail). For this example, actions can be things such as getUserInformation
, getUserInformationSuccess
and getUserInformationFailed
. You can think of actions as triggers, you use them to trigger either a reducer or an effect, which we will move on to shortly.
You typically define your actions in a dedicated file for that scope, e.g. user.actions.ts.
You define an action with the following syntax:
Actions are classes that have two properties: Type & Payload.
The type is a read only string describing what the action stands for and in which component is located. For example: ‘[User] Get User Information’
The payload property depends on what type of data the action needs to send to the reducer. The props function is what you use to set the interface for the payload of each action.
In this example, the payload for the success action is the user information, and for the failed action the payload is the error message. It’s important to mention that an action not necessarily needs to pass a payload.
For our first action, we will listen for it in an effect’s file. Effects are used when information needs to be fetched from outside our store, or when we want to have side effects from our actions.
For our second and third action, we will listen to that in a reducer’s file.
Dispatching an action
To dispatch an action, import the Store
into your component and call the dispatch
method with your action.
Creating and Effect
Effects are used when we need some data from an external source, such as an API. It can also be for triggering other things in our application as a side effect of actions.
Effects are stored in a normal injectable class, meaning that you can inject your http service to fetch data.
Let’s register an effect in our newly created user.effects.ts
.
ofType: We pass the action that we want to listen. This effect is now listening out for when this getUserInfotmation
action is triggered (dispatched in our user.component.ts
).
mergeMap: Because we are expecting an observable from the userService
, we’ll use a mergeMap
operator from RxJS to process our observable.
Note: Typically, you return an action from an effect (side effects). However, you don’t have to. You can use { dispatch: false }
at the end of your effect and this means that NgRx won’t be listening out for any actions returned by this effect.
Creating Reducers
A reducer is a set of pure functions that take the payload of an action and set it into our store.
Underneath our initial state declaration, we create our reducers, e.g. user.reducer.ts
:
We first pass in our initialState
. After that, we use on
to listen to a particular action, and then use a callback function that has the current state object and the action payload as parameters. Whatever we return from this function will become the new state.
We can declare as many functions in this reducer as we want, simply by adding more on()
functions after the one we have already set.
Creating Selectors
Selectors are functions created to extract a state from the store, e.g. user.selectors.ts
:
Using a selector
We can now use the selectors from our store, from within any component that we want as observables and display on our HTML using the async
pipe.
The great thing about using NgRx is that because it uses observables, our state will always be in sync. In the future, if we have more actions that eventually modify the state, the component declared above will always receive the most updated version of the store.
Final thoughts
It has been a long ride since we started revisiting the key concepts until we took everything into action. Thank you for bear with me, I hope this information helps you through your journey across state management and reactive programming.
Finally, I’d like to share this list of Pros and Cons as a summary of the implications of adding NgRx into your application so you can make your final decision:
Pros of using NgRx
- Single source of truth so applications are going to behave more consistently
- Easier to test since we are introducing pure functions to handle changes in the state
- Once you feel comfortable using NgRx and understanding the flow of data, your application will become incredibly easy and predictable
- Learning curve not so steep in my opinion
- Easier to debug
Cons of using NgRx
- Every time you add a new property to the state, it will come together with adding actions, reducers, selectors, and effects
- The initial setup takes time and a considerable amount of code with and increasingly use of RxJS operators and observables all over the place
- Adding a library that is going to be a big dependency for your app
As a conclusion, I can tell that NgRx excels in managing complex states, making it ideal for applications with a lot of user interactions and multiple data sources. Now it’s up to you to decide if your application is a good candidate to implement NgRx for state management.
Happy coding! ♥