DEV Community

Cover image for Why You Should Be Using TransferState (& Scully) to Cache Your API Calls in Angular
Jennifer Wadella for Bitovi

Posted on • Edited on • Originally published at jenniferwadella.com

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

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);
  }


Enter fullscreen mode Exit fullscreen mode

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')
  }


Enter fullscreen mode Exit fullscreen mode

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')
    )
  }


Enter fullscreen mode Exit fullscreen mode

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.

Top comments (7)

Collapse
 
sumitdhameja profile image
Sumeet Dhameja

Thanks for this post Jennifer. When building, I am not seeing any data.json created for that route when running "npm run scully" Is there any config that needs to be enabled?

Collapse
 
hussainb profile image
Hussain

Hi Sumeet, I followed the steps mentioned in this article and the data.json did not get generated for me too.. were you able to make this work for you?

Collapse
 
sumitdhameja profile image
Sumeet Dhameja • Edited

Hi Hussain,
It was long time ago, but I think I had a token interceptor where i was intercepting request and adding Auth token in the header and that was causing the issue for me. When building I used "isScullyRunning" to check and skip it by calling "next"

if (isScullyRunning()) {
      return next.handle(request);
    }
Enter fullscreen mode Exit fullscreen mode

Hope it helps!

Collapse
 
hussainb profile image
Hussain

Jennifer, After adding the useScullyTransferState as per your article, it does not generate any data.json for the routes. is there anything more to be done apart from using useScullyTransferState?
thanks!

Collapse
 
ninjamonz profile image
ninjamonz

I guess 'useScullyTransferState' not yet documented on the docs. Happy to know that from here. Thanks.

Collapse
 
likeomgitsfeday profile image
Jennifer Wadella

Yep! That's why I wrote this post! I know the Scully team is working really hard - writing documentation is tough!

Collapse
 
jwp profile image
John Peters

Wow, thanks Jennifer...