In this series of articles, I am sharing the lessons that I have learned from building reactive applications in Angular using NgRx state management. My introduction explained how I came to use NgRx. Going forward, I will share best practices in the form of an example application, Eternal. Here, we’ll look at the way state management lets you add caching functionality to your code.
If you prefer watching over reading, here is the video version:
You can find the sources files on
rainerhahnekamp / ngrx-best-practices
Best Practices & Design Patterns for NgRx
Cache & Load Status
This pattern ensures that the store does not load data it already has. In other words: It adds a caching functionality.
We create this pattern in two steps. The state gets an additional property called loadStatus, which it uses internally to determine if a request to an endpoint is required.
State management tutorials usually use a load and a loaded action to implement an endpoint request. Our pattern adds a third action called get. Components should only use the get action. The load action is for internal use in state management only.
The diagram below roughly shows in what order actions, effects and reducers work together to load the data against an empty state.
If the state already has data, components can dispatch the get Action as often as they want. It will not result in unnecessary requests:
Example
In our example, there is a component that lists customers and another component that shows a detail form.
Both components need to dispatch the load method. They need the customer data and have to make sure it is loaded.
One could argue that users always follow the path from the overview to the detail view. So it should be enough for only the list view to dispatch the action.
We cannot solely rely on that. Users can have a deep link directly to the form. Maybe some other components in the application directly link there as well.
We do now have the problem that “clicking through the user list” will end up creating lots of unnecessary endpoint calls.
In order to solve that, we introduce a loadStatus
property.
The data in the store can be in three different states. It can be not loaded, it can load or it is loaded. Additionally, we only want to render our components, when the data is present.
LoadStatus
is a union type with three different values. The state holds it as property and its initial value is set to “NOT_LOADED”.
The state changes from
to
We introduce a further action, that we call get. The components will only use that action. In contrast to the load method, the get notifies the store that there is a need for the data.
An effect handles that get method. It checks the current state and, if the state is not “LOADED”, it dispatches the actual load action. Note that the load action is now an “internal” action. Components or services should never dispatch it.
Next to the effect that deals with load action, we also have an additional reducer. It sets loadStatus
to “LOADING”. This has the nice benefit that parallel requests cannot happen. That’s secured by design.
The last thing we have to do is to modify our selectors. They should only emit the data if loadStatus
is set to "LOADED". Consequently, our components can only render if the data is fully available.
Further Considerations
Why can’t we just take a null value instead of loadStatus
as an indicator that the state hasn’t been loaded yet? As consumers of the state, we might not know the initial value so we can only guess whether it is null or not. Null may actually be the initial value we received from the backend. Or it may be some other value. Having an explicit loadStatus
value, we can be sure.
The same is also true if we are dealing with an array. Does an empty array mean that the store has just been initialized or does it mean that we really don’t have any data? We don’t want to show the user “Sorry, no data has been found” when – in reality – the request waits for the response.
Advanced Use Cases only
With complex UIs, the store can easily receive multiple actions in a very short period of time. When different components fire load actions, for example, all of these actions together build up state that some other component wants to display.
A similar use case might be a chain of actions. Again, a dependent component only wants to render when the last action is through.
Without the loadStatus
property, the selector in the component would emit each time the state changes partially. This can result in a user-unfriendly flickering effect.
Instead, the selectors should first check against the loadStatus
before returning the actual data. That has the nice benefit that the component gets the data only once and at the right time. Very efficient and performant!
Top comments (1)
Cool