Why You Should Be Using TransferState (& Scully) to Cache Your API Calls in Angular

Learn how to improve Angular app performance and user experience by caching calls to your API using Angular TransferState and useScullyTransferState.

May 22, 2020

Caching API calls that return largely static data and be a great way to improve application performance AND save $$$ by limiting server requests.

For example, an e-commerce site with products might benefit greatly from caching API calls to fetch lists of those products and re-deploying when new items are added. Caching an API call means making the HTTP request when we statically generate our application pages, and storing the results of that request locally, like in a json file, to be served from our CDN. This prevents the user from having to make the HTTP request to where ever the server it lives on is and wait for the response every time they view a page of our app!

There are added security benefits to this approach as well - we’re not exposing our API in the browser at all!

TransferState in Angular

For caching data, Angular provides a TransferState API as a way to cache responses from HTTP requests and put them in a statically generated page.

// my-service.service.ts

import { TransferState, makeStateKey } from '@angular/platform-browser';

  constructor(private http: HttpClient, private ngState: TransferState) { }

  getVillagers(): Observable<Villager[]> {
    const villagersKey = makeStateKey('villagers');
    const cachedResponse = this.ngState.get(villagersKey, null);

    if (!cachedResponse) {
      return this.http.get<Villager[]>('http://acnhapi.com/villagers').pipe(
        tap((res) => this.ngState.set(villagersKey, res))
      )
    }

    return of(cachedResponse);
  }

There’s quite a bit of setup work that goes into using it and configuring how to serve the application properly. (example here if you’re curious)

Scully-flavored TransferState

I’m clearly a huge fan of Scully as a JAMstack tool, and their approach to caching is chefs kiss.

Scully has abstracted some logic around using TransferState to make it super simple for developers to cache API calls with their useScullyTransferState method.

The useScullyTransferState accepts 2 params, the key you want to store your data under, and an Observable of the original state of what you’re working with. In the following example, our original state will be the GET request we’re making with HTTPClient.

In my Animal Crossing Field guide application, I have a service file where I have all of my HTTP requests.

Here is my getVillagers request that returns a list of all villagers in Animal Crossing New Horizons, and YIKES there’s 391! This large amount of data I’m requesting is very static and is the perfect use-case for caching those requests + limiting calls to the free 3rd party API I’m using.

// my-service.service.ts
  getVillagers(): Observable<Villager[]> {
    return this.http.get<Villager[]>('http://acnhapi.com/villagers')
  }

Let’s use useScullyTransferState to cache the results of this call. First, import TransferStateService from Scully and inject it into our service.

// my-service.service.ts
import { TransferStateService } from '@scullyio/ng-lib';
...

constructor(
   private http: HttpClient, 
   private transferState: TransferStateService
) { }

  getVillagers(): Observable<Villager[]> {
    this.transferState.useScullyTransferState(
      'villagers',
      this.http.get<Villager[]>('http://acnhapi.com/villagers')
    )
  }

Now, rerun ng build, followed by npm run scully. You may notice something happening in your terminal output. Every page you are statically generating with Scully that has an HTTP request using the TransferStateService is getting a data.json file created for it.

Alt Text

Scully’s doing a few really cool things for us.

  1. If we’re just in development mode, (vs. serving our generated static files), Scully will treat the API call as normal, the HTTP request will execute every time.
  2. The magic happens when we serve our statically generated app files. When we run ‘npm run scully’ to generate our files, Scully will make the HTTP request for us then store the results in a data.json. This data.json file lives next to the index.html file in the directory of the page we generated, to be served from the CDN. Again, this prevents the user from having to make the HTTP request to where ever the server it lives on is and wait for the response!

To really be clear, any page statically generated by Scully that makes an HTTP request you’ve returned with the useScullyTransferState will cache the response of that request by storing it in a data.json file that is served on your static page. 🎉 🎉 🎉

Caveats

Before you go CACHE ALL THE THINGSSSSS, do consider how users are interacting with your application. If there’s heavy modification of the data, like a to-do list, implementing API caching may not give you much in terms of performance boost or improved user experience.

Be aware that if you’re using this approach, that same data.json file will be served until you generate a new build. If your API changes, new items are added, etc, those won’t be reflected in the statically served data.json file until you run a new build. I call this out because if you’re new to the JAMstack approach and don’t have automated builds for every time your content (including data delivered by your API) changes, users may not be getting the latest data.