NGRX: how it works

v.18

v.18

Fabio Biondi
Google Developer Expert
Microsoft MVP
# NGRX lifecycle
This is the official NGRX diagram.
At first it might seem complicated and certainly unclear so let me explain it step by step and provide several real world examples.
The diagram shows how a component can dispatch one or more actions to notify that something happens.
After a dispatch there are two possibilities:
The action does not require side effects, such as Http requests, router redirects and so on.
It's used just to notify that something is happened and it only requires a state update.
In this scenario, one or more reducers takes care of the action and update their portion of the state consequently.
Components can then selects any portion of the store using selectors and bind their value with the UI.
2. Async with side effects
When the action requires a side effect to be performed ( i.e. HTTP calls, router redirects, write on localstorage, ...) , one or more effects takes care of the action and execute the side effect (i.e. invoking the endpoint or other operations).
At the completion of an asynchronous operation, the effect dispatches a new action to notify the success or failure of the operation.
For instance after a successfully http requests, a "success" action can be dispatched by the effect passing the request result as payload.
Now reducers takes care of the actions and update the state consequently.
Components can then use selectors to get this portion of the state when available.
Separation of Concerns
This structured separation in NGRX promotes modularity, maintainability, and scalability in Angular applications, allowing developers to focus on individual aspects of the application without intermingling concerns.
-
Actions encapsulate state changes as plain objects, allowing components to dispatch actions without knowing the underlying implementation.
-
Reducers, which are pure functions, handle these actions and determine how the state should be updated, ensuring a clear separation between the logic that modifies state and the state itself.
-
Selectors further enhance this separation by providing a structured way to query state, enabling components to access only the necessary pieces of state without being aware of the entire state structure.
-
Effects manage side effects, such as asynchronous operations and API calls, isolating them from both the state and the components, which simplifies testing and maintenance.
Let me show you and simplify some common scenarios
# "Sync" demo with no side effects
In this scenario, when we dispatch an action we just want to notify that the state should be updated.
- Actions: are dispatched (usually by components)
- Reducers: one or more reducers take care of these actions and update the state consequently
- Selectors: components allows to query the store
- Actions: the component dispatches the
ChangeLanguage
to notify the change of the language or OpenModal
to notify that a modal should opened.
- Reducers: reducers takes care of these actions and update the state consequently, i.e. saving the new language (
it
/en
) in the store or the modal opening status (true
/false
).
- Selectors: are used to select the current language or the status of the modal, if it's opened or not
The diagram shows how the opening of a modal could be handled:
# "Async" Demo: with side effects
In this scenario, when we dispatch an action we also want to execute a side effect (usually an asynchronous operation) before the state update.
After the side effect we might update state or not, it depends from the use case.
- Actions: components dispatch actions. i.e.
AddUser
- Effects: an effect listens for this action and make the necessary HTTP requests to add the user to the database. At the completion, the effect dispatches a new action again, notifying the failure or success of the operation. In case of success it also passes the information retrieved from the server (i.e. the full user object with the
id
assigned by the database)
- Reducers: one or more reducers take care of these actions (success/fail) and update the state consequently (i.e. saving the new user to the list of available users in the global store)
- Selectors: UI components, directives or services can read the store using selectors: i.e. we can get the list of users or just a filtered portion.
Watch the short video below to see an animated version of this flow:
# Async Example: handle HTTP errors
How can we handle errors?
When an http request fails, the effect can emits an error action, for example AddUserFails
.
But there are several ways to handle this error so I show you some of the possibilities:
1. State update ( in reducers )
We can handle the error in the reducers, for instance saving the error status
/ message
or simply a boolean
in the store, to indicate there is an error: i.e. error: true
.
A component can then use a selector to get the error
value and display an error message in the HTML template when it's true
or it contains an HTTP error status:
However, we may not want to update the state in case of errors but only show a toast notification.
For instance we could dispatch the AddUserFails
action when the http request fails, exactly as before.
This time we won't update the state anymore but we might have another NGRX effect that handles the AddUserFails
action and makes something else, for example display a toast notification:
This solution does not exclude the previous one where we updated the state. In fact we could trigger the (notification) effect and save something in the store at the same time.
3. Generic Actions and Effects
In the previous example we have dispatched an action with a specific name: AddUserFails
, so it's clear that the user insertion operation failed.
Anyway we can also choose a more generic name to dispatch an action every time there is HTTP error, for instance showError
, passing as action payload the type of error or other useful information about the scenarios that has generated it.
In this way we can define a generic showError
effect that simply show a notification message:
The showError
effect might also dispatches another action to update the state, it depends of what we are doing.
The advantage of this approach is that we can reuse this generic action multiple times in several scenarios for multiple type of errors:
- first of all it reduces the boilerplate and the need to create different actions (that as you'll see in the next lessons it requires a file and some code to write)
- and we also have a way to globally handling all errors with a single action type in only one reducer.
Anyway, these are just a few examples.
Feel free to find other approaches and invent your own strategy.
# Cheatsheet PDF