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:
- Setup Angular App
- Add @ngneat/cashew
- Add to HTTP service
- 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
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
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>
posts.component.ts
posts$?: Observable;
constructor(private coreService: CoreService) {
this.posts$ = coreService.getPosts();
}
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);
}
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 },
];
Done with the chores!
Add @ngneat/cashew
Now, let us install @ngneat/cashew package:
npm i @ngneat/cashew
Adding cashew to app module:
app.module.ts
...
import { HttpCacheInterceptorModule } from '@ngneat/cashew';
@NgModule({
declarations: [...],
imports: [
...
HttpCacheInterceptorModule.forRoot(),
],
providers: [],
bootstrap: [...],
})
export class AppModule {}
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);
}
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 {}
Last thing is adding version and key to the “withCache” method’s object:
core.service.ts
...
private options = {
context: withCache({
version: 'v1',
key: 'omelet'
})
};
...
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
})
};
Git Repository
Check out the git repository for this project or download the code.
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.