The benefits of adding rx-query to your Angular project

profile
Tim Deschryver
timdeschryver.dev

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? Because adding rx-query is no extra work, and you get some useful features for free.

A query has a status link

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 (success, error, loading, 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:

A loading indicator is visible while the request is pending, when the request returns a response the heroes are shown.

A query can have an input stream link

The 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.

format_quote

NOTE: For these queries, the input will be appended (by using the JSON.stringify method) to the key to generate a unique key.

A query is cached link

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:

  1. it returns the cached data while the data is refreshed in the background;
  2. it can ignore over-fetches when the state is still considered "fresh";
  3. 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.

The initial behavior loses its previous state, thus the view loads with no data.
Because the dashboard and the details are persisted in the cache, the view loads immediately.

Prefetch link

We can use this cache to prefetch data so that the user doesn't have to wait after a navigation. The 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.

The details are directly visible because they are prefetched.
format_quote

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.

A query is retried link

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.

A query is refreshed link

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.

A query can mutate link

Besides fetching data, rx-query also provides an API to save data with the mutate method. Here again, 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 hero name is updated directly when the HTTP request is fired.

Update methods link

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.

Therefore, rx-query exposes helper methods to update the state manually.

By using the mutate methods we can update the state from another cache, this makes the update instantly.

Wrapping up link

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:

As a side note, rx-query can be more than a simple wrapper around an HTTP request at the component level. It can:

To start using rx-query, install the package with the following command.

To see it in action, take a look at the live examples. The source code of these examples can be found in the repository.

Feel free to update this blog post on GitHub, thanks in advance!

Join My Newsletter (WIP)

Join my weekly newsletter to receive my latest blog posts and bits, directly in your inbox.

Support me

I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.

Buy Me a Coffee at ko-fi.com PayPal logo

Share this post