DEV Community

Zaki Mohammed
Zaki Mohammed

Posted on • Originally published at zakimohammed.Medium on

Cache Angular HTTP request using @ngneat/cashew

The time of not wasting time on caching HTTP request data has come. Presenting @ngneat/cashew package to do the needfully; it takes our stress of manually creating service variables to hold up the HTTP requests data again and again. In this article we will explore @ngneat/cashew dope package for caching HTTP requests.

In any Angular project we create HTTP services to make API calls GET, POST, etc. to operate on server data. Now, in order to preserve the loaded data, we usually create service variables to hold up the data until user make a reload or login again into the system. For this we have to maintain this variable with our bare hand which kind of repetitive task for each newly created service. Storing the response data like forever is not what we usually get paid for. Here, comes super chill @ngneat/cashew package!

We will head in this direction:

  1. Setup Angular App
  2. Add @ngneat/cashew
  3. Add to HTTP service
  4. Use LocalStorage

Setup Angular App

Let us first do the daily chores of any Angular app and as per the need of the hour we are considering v16 of Angular; just for fun we are using Cirrus UI:

ng new ng-cashew-app

npm i cirrus-ui
Enter fullscreen mode Exit fullscreen mode

Below shows the skeleton of the project:

ng-cashew-app
|-- src
| |-- app
| | |-- core
| | | |-- components
| | | | |-- footer
| | | | |-- header
| | | |-- pages
| | | | |-- home
| | | |-- services
| | | |-- core.service.ts
| | |-- modules
| | | |-- posts
| | | |-- users
| | |-- app-routing.module.ts
| | |-- app.component.html
| | |-- app.component.ts
| | |-- app.module.ts
|-- angular.json
|-- package.json
Enter fullscreen mode Exit fullscreen mode

This application respect minimalism and have less number of routes and components. For the sake of understanding how compression affects the lazy loaded modules we have added 2 feature standalone modules Posts and Users. Thanks to Angular v16 these are standalone components routed in a lazy loaded fashion. The data is coming from our beloved JSON Placeholder API.

Below shows the business logic for Posts component, the Users component is also created in the similar manner:

posts.component.html

<div class="section">
  <div class="content">
    <div class="row mb-3" *ngFor="let post of posts$ | async">
      <div class="col-sm-4">
        <img
          class="img-stretch u-round-md"
          [src]="getImage(post)"
          alt="img-stretch"
          height="400"
          (error)="getDefaultImage($event)" />
      </div>
      <div class="col-lg-6 col-md-8">
        <h3>{{ post.title | titlecase }}</h3>
        <p>{{ post.body }}</p>
        <div>
          <span class="icon">
            <i class="far fa-wrapper fa-clock" aria-hidden="true"></i>
          </span>
          {{randomDate | date: 'fullDate'}}
        </div>
      </div>
    </div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

posts.component.ts

posts$?: Observable;

constructor(private coreService: CoreService) {
  this.posts$ = coreService.getPosts();
}
Enter fullscreen mode Exit fullscreen mode

core.service.ts

private url: string = environment.apiUrl;
private postUrl: string = 'posts';
private userUrl: string = 'users';

constructor(private http: HttpClient) {}

getPosts() {
  const url = `${this.url}${this.postUrl}?userId=1`;
  return this.http.get(url);
}

getUsers() {
  const url = `${this.url}${this.userUrl}`;
  return this.http.get(url);
}
Enter fullscreen mode Exit fullscreen mode

app-routing.module.ts

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'posts', loadComponent: () => import('./modules/posts/posts.component').then(x => x.PostsComponent) },
  { path: 'users', loadComponent: () => import('./modules/users/users.component').then(x => x.UsersComponent) },
  { path: '**', component: HomeComponent },
];
Enter fullscreen mode Exit fullscreen mode

Done with the chores!

Add @ngneat/cashew

Now, let us install @ngneat/cashew package:

npm i @ngneat/cashew
Enter fullscreen mode Exit fullscreen mode

Adding cashew to app module:

app.module.ts

...
import { HttpCacheInterceptorModule } from '@ngneat/cashew';

@NgModule({
  declarations: [...],
  imports: [
    ...
    HttpCacheInterceptorModule.forRoot(), 
  ],
  providers: [],
  bootstrap: [...],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Add to HTTP service

Time to add the cashew config to the HTTP options:

core.service.ts

import { withCache } from '@ngneat/cashew';
...

private options = {
  context: withCache()
};

getPosts() {
  const url = `${this.url}${this.postUrl}?userId=1`;
  return this.http.get(url, this.options);
}

getUsers() {
  const url = `${this.url}${this.userUrl}`;
  return this.http.get(url, this.options);
}
Enter fullscreen mode Exit fullscreen mode

Here, the options object holds the context property which is coming from the withCache() method of cashew package. We have added this object as second parameter in the HTTP’s get method for post and users endpoints.

Just simply adding withCache() method gives your service methods wings of cache. If you check the network tab you will only find a single call being made to the endpoint, and if you revisit the pages after navigating to some other route, you won’t find any calls to the server.

Use LocalStorage

Another question your senior dev in your team will ask is, what about if I refresh the page, will it persist the data in cache? You simply need to assure them that, we need to configure @ngneat/cashew to use localStorage instead of run time memory. This is as simple as eating an omelet (omelet reference!).

We need to add “useHttpCacheLocalStorage” to the providers of app module:

app.module.ts

...
import { HttpCacheInterceptorModule, useHttpCacheLocalStorage } from '@ngneat/cashew';

@NgModule({
  declarations: [...],
  imports: [...],
  providers: [useHttpCacheLocalStorage],
  bootstrap: [...],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Last thing is adding version and key to the “withCache” method’s object:

core.service.ts

...

private options = {
  context: withCache({
      version: 'v1',
      key: 'omelet'
    })
};

...
Enter fullscreen mode Exit fullscreen mode

Done! Thats it, now we can see the localStorage for this change. Just go to either “posts” or “users” route and check the “Application” tab of developer too and go to “Local Storage”. You will see something like this in your local storage.

Additionally, we can set the “ttl” (in milliseconds) for this, by default it is for 1 hour. The version is a trick to start using a new local storage as a cache to disband the old cache.

private options = {
  context: withCache({
      version: 'v1',
      key: 'omelet',
      ttl: 3000
    })
};
Enter fullscreen mode Exit fullscreen mode

Git Repository

Check out the git repository for this project or download the code.

Application

Download Code

Git Repository

Summary

Caching is developer’s love and if it can happen in frontend too with this much of little effort, it’s a salvation! You can clearly feel that how a fan boy I am for this package. This actually given us good grades in our performance audits. You can simply consider @ngneat/cashew as a plug and plaything. It also has some hackable areas, worth giving a try, providing full blown customization. Consider this a performance boosting thing for your next or current app.

Hope this article helps.

Originally published at https://codeomelet.com.

Top comments (0)