In this post, we're going to add
rx-query to the Angular Tour of Heroes while pointing out the benefits of
rx-query. Before we start, I just want to mention that
rx-query is inspired by react-query and SWR.
rx-query provides an easy way to fetch data over HTTP.
This is already the case withing Angular applications, so why should you care?
rx-query is no extra work, and you get some useful features for free.
Making an HTTP request with
rx-query is almost as simple as a normal request, just wrap the request with the
query method and give it the query a key. The key is to distinguish multiple queries, it will become clear why this is important in the next sections.
Just like a normal request, the
query method returns an Observable (
Observable<QueryOutput<T>>). This Observable emits a value for each stage of the request (
refreshing). This is exposed with the
status property on the output, and by using the status in combination with the
ngSwitch directive it's easy to show a different view for each stage of the request.
While it's not required to create a view for the different stages, it requires the attention of the developer to think about the un-happy paths. Leading to a better user experience.
Resulting in the following:
heroes-list query doesn't require an input because it's just fetching the whole list of heroes.
For queries that require an input parameter, there's an overload on
query where you can pass a static parameter or an Observable parameter.
This makes it easy to use, for us as developers.
When an Observable is used as the input, the query callback is invoked, with the (unwrapped) value, when the Observable emits a new value. This is useful for components that need to fetch data depending on a route parameter, for example, the details page of a hero.
NOTE: For these queries, the input will be appended (by using the
JSON.stringifymethod) to the key to generate a unique key.
The reason you have to provide a key to the query is so that
rx-query can cache the query.
The caching layer has three benefits:
- it returns the cached data while the data is refreshed in the background;
- it can ignore over-fetches when the state is still considered "fresh";
- it ignores incoming requests for the same key while the same request is already pending;
Because a query is cached the application feels faster than the default behavior. This is visible in the following two GIFs.
We can use this cache to prefetch data so that the user doesn't have to wait after a navigation.
prefetch method has the same signature as the
query method but it doesn't return a result.
If we create a reusable
prefetch directive (like the one below), it becomes an easy task to prefetch data.
Then we can prefetch the hero details as follow.
Now, when a user navigates to the detail view, the details are instantly visible.
There are other solutions to achieve the same result, for example with NgRx, which I wrote about in Making your application feel faster by prefetching data with NgRx.
Sometimes a request can fail because the server timed out or when the server is in a bad state.
Before a query ends up in the
error state, the query will be retried 3 times in the hope that it receives a successful response.
Because of this practice, the user experience is improved.
If there is already data present in the cache, that data will be used while a retry is pending.
The behavior is the same for when there is no data present, the query will stay in the
loading state until the maximum numbers of retries are reached.
State that is stored client site becomes stale. That's why
rx-query offers multiple options to refresh its state.
Besides having a refetch after x milliseconds, it's also configurable to refetch the request when the window receives the focus.
This makes sure that the user will always work with a fresh state.
Besides fetching data,
rx-query also provides an API to save data with the
rx-query helps to make the application feel faster because it's using optimistic updates.
Meaning that the state in the cache will be updated before the request is sent to the server.
If the request should fail, the cache automatically performs a rollback to its previous state.
To mutate the state, the
mutator must be configured:
To invoke the mutation, use the
mutate method on the
QueryOutput with the updated entity as the argument.
The above GIF shows a problem. While the hero detail is updated, the dashboard still shows the hero detail from before the update. It's only after the refresh of heroes list query, that the update is visible on the dashboard.
rx-query exposes helper methods to update the state manually.
rx-query has multiple benefits all aimed to improve the user experience and by keeping good developer ergonomics in mind.
From a functional side, the cache and refresh configuration options help to make your application feel faster, the automatic retries help to make the application more robust.
The developer receives configurable options to tweak the queries because every situation can be treated differently.
rx-query forces the developer to think about how state is used. While working with
rx-query, some of the following questions will emerge; Should it be cached, if so for how long? When do I want to refresh the state? What should we do when a request fails?
The benefits are clearly visible within the Tour of Heroes:
- the application is faster;
- the component code is trimmed down;
- there is an increase in the template code (HTML) to handle the different stages of the request;
- while working with cached state, it's easy to keep the state "fresh";
As a side note,
rx-query can be more than a simple wrapper around an HTTP request at the component level.
- also be used in combination with other packages, for example with @ngrx/component-store, where it can update the component state based on the query output
- also be used at a global level, for example with @ngrx/effects, where it can dispatch actions based on the query output
To start using
rx-query, install the package with the following command.
Please consider supporting me if have you enjoyed this post and found it useful: