Stop storing server query in NGRX
Please don't use NGRX as server query cache
Let's examine this simple scenario:
- Angular application
- with service that fetch a list of github repositories
- Display the list of repositories in
AppComponent
Everything is working great, but then you have to use the same list of repositories in another component. You could just call the service again, but that would be an unnecessary call and you can simply cache the response (what if you need to use it in 100 places, does that mean you will have to send 100 of those requests?). So you decide to store the list of repositories in NGRX store.
There are few ways that can be achieved (which are all wrong):
- Dispatch proper actions in components (worst of the wrong solutions)
- Use
@ngrx/effects
and make each component dispatch an intent to read that data (a bit better) - Use
@ngrx/data
which will save you a lot of boilerplate code, but still requires you to configure it properly (best of the wrong solutions).
Our recommendation: DON'T USE STATE MANAGEMENT LIBRARY AS SERVER QUERY CACHE
The problem with storing server query in NGRX
If you are using NGRX to store server query, there is a lot of complexity you will have to face. This complexity today has led me to the believe that there is application state, and query cache, and those things should be seperated.
Using NGRX to store server query has lots of boilerplate code, and it is not easy to get it right. In short I would recommend today a side from using state management library (currently NGRX is the most popular) I would recommend to use additional library for sending server queries, which will also cache the responses (I recommend using Angular Query - @ngneat/query).
Let's see what are the problems with storing server query in NGRX.
@ngrx/effects
If you are using @ngrx/effects
to store server query, here is all the things you will have to do:
- create reducer - you can use
@ngrx/entity
to help you with that (will help you also with selectors) - create action to set the list of repositories
- create effect for acting on components intent to read the data
All the things above will consist of ton of boiler plate code, not to mention that we are not dealing properly with stale data (will be discussed later).
@ngrx/data
The goal of @ngrx/data
is to reduce the boilerplate code, and it works nice if your backend api is exactly like the conventions they expect (which 99% is not the case). And then you will have to face configuring @ngrx/data
which is not such a simple case and bad documentation makes it even harder to understand how to configure.
Morover, look at this diagram which is posted in @ngrx/data
:
server query cache needs to be simple, most of the developers that are using @ngrx/data
I'm not convincede that they fully understand what actually happens, which makes it even harder to understand and debug once there are issues.
The solution - @ngneat/query
There is a very popular data-fetching library for web applications called Tanstack Query. This library can be used in React, Solid, Vue, Svelte and Angular.
@ngneat/query is the Tanstack Query implementation for Angular which is based on the @tanstack/query-core
package and similar api to the @tanstack
packages, to give you the power of Tanstack Query to Angular.
The main features are:
- Caching
- Avoid duplicate requests
- Update out of date data in background
- dealing with stale data
- Managing memory and garbage collection of cached data
- Dev tools to examine the cache content
In the following example we have 2 components that needs the list of repositories, one which displays the list and another component that display the number of repositories.
Instead of sending 2 queries lets send one query and also define that the data is stale after 30 seconds and needs to be refreshed if there are any changes.
This task would have been complex if we did our own solution, but using the power of Tanstack and @ngneat/query
it becomes easy.
Notice that on the GithubService
I can define the staleTime
which means that the data will be stale after 30 seconds, which means it will take the data from the cache but after 30 seconds will also refetch in the background (the user sees the result right away and after refetch his data will be updated).
We also included the query devtools that allows us to inspect the content of our cache.
Conclusion
Using NGRX to store server query is a bad idea, it is complex and hard to get it right. Using a dedicated library for server queries and cache will reduce the amount of complexity and work much better.