TL;DR: You need to cache the observable returned from HttpClient and combine it with shareReplay and catchError.

There’s a lot of situations when we need to ask the server the same thing again and again. Typical examples are asking for translations for the terms, or resolving some codes using the vocabulary located on a server.

We don’t want to bother the server too much, so we use caching. In this article, I will try to explain the proper way to cache HTTP calls using Angular and RxJS

No Caching

So, suppose that we have a server that can return some nutrition information about a certain product. Let’s use the Open Food Facts API for that.

I give it the code of the produce (e.g. "7613034626844") and it gives me back an object with all the necessary information (e.g. {name: "Ovomaltine milk powder"}). Let’s sketch an Angular service for that (see StackBlitz):

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient, HttpParams } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  
  private readonly URL = 'https://world.openfoodfacts.org/api/v0/product/';

  constructor(private http: HttpClient) {
  }

  resolveProduct(code: string): Observable<any> {
    console.log('Request is sent!')
    return this.http.get(this.URL+code+'.json');
  }
}

The service takes the code and passes it as a parameter to the API. We will create a super simple ProductComponent and call it in the constructor.

Now, let’s use our ProductComponent like so in the AppComponent:

Sure enough, we will see that the request was performed three times, even though we were always asking the same information. Now, let’s make it faster using caching.


You may also like: Angular 8 RxJS Multiple HTTP Request using the forkJoin Example


Naïve Caching of the Value

The first idea for caching would be to actually cache the returned term, as I did here (see StackBlitz):

In theory, our service checks if the given term is presented in the cache, and, if so, returns it. If the product code is new, it asks the HTTP service to call it. When the server sends us the response, we put it into cache using the tap operator.

For example, a user clicks a button to see the info about the code "7613034626844" and our service will execute a call to a server.

When the users clicks the button next time to resolve the same term, our service will not initiate an HTTP call, using the cached value instead. At the first glance, everything works fine.

Problems with the naïve approach

However, there is a caveat: we put the value into the cache asynchronously, and all asynchronous code will be executed after the synchronous code.

What if we call the resolve function in a for loop or in *ngFor or just have several components using it within the same template? It turns out that our cache doesn’t do its job:

angular

Suddenly, in the network tab, we see a lot of identical HTTP requests, so apparently, the cache can’t catch up.

angular

The problem is that the reactions to HTTP calls happen only after all those HTTP calls are executed, because every asynchronous code is happening in the event loop.

Less Naïve Version — Cache the Observable Itself

We can fix this problem quite easily. Instead of caching the return values from the server, we can cache the observables that the Angular HttpClient service returns.

This way, both function calling and caching will be synchronous. The only trick is that we must use the shareReplay operator that will allow the subscribers to view the result of the HTTP call.

Without shareReplay, the observable will be kept in the FINISHED state after the request, and the new subscribers will not be able to get its value.

The result in the network tab looks exactly as we planned, we only have one HTTP call. Now, all that’s left is to manage the exceptions.

Dealing with exceptions

What will happen if our server was down for a couple of seconds while we were asking it to resolve a term for that? The error HTTP code will raise an error in the observable.

After the observable is errored or finished, there’s nothing we can do to restart it — that’s a contract for the observables.

With our current approach, it means that we’re caching the errors which is not a reasonable thing to do: the server will eventually start working and we would like to send it an HTTP request.

To achieve that, we can use the catchError operator which would delete an observable from the cache, so that, next time, the server will be called.

You can use the [delete](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete) operator for that, or assign false to that key in the cache if you care about the performance more than readability.

The result: now we call the API only once.

angular

Analysis

This particular caching approach is called memoization and it will serve you well in the following cases:

  • The user asks for the same information a lot of times.
  • This information on a server is stable by nature. Use it for vocabularies, translations, lookup services.
  • Your application is not supposed to run for many days without reload.

All shortcomings of the memoization have a common reason, that is, we never clean our cached values. With that in mind, here are some restrictions:

  • Don’t use this caching method for checking real-time information: prices of stocks, weather, Twitter feed, etc. It simply won’t work.
  • Don’t use this caching method if you expect your application to run for months and years without reload, because the cache size will become terribly big and you will have memory leaks.

Originally published by Yury Katkov at medium.com

#angular #web-development

How to store HTTP calls using Angular and RxJS.
3 Likes121.05 GEEK