How to do caching in Angular 17?

Welcome back to AyyazTech! In today's post, we're diving deep into the world of caching in Angular 17. Effective caching can significantly enhance the performance of your Angular applications by reducing unnecessary HTTP requests and optimizing data retrieval. In this guide, we'll explore various caching techniques, including using services, observables, interceptors, and local storage.

Introduction to Caching in Angular

Caching is a critical aspect of web development that helps in storing data temporarily to serve future requests faster. Angular 17 offers multiple methods to implement caching, which we will cover in this article. By the end of this post, you'll have a solid understanding of how to optimize your Angular applications with efficient caching mechanisms.

Setting Up Your Angular Project

Before we start, ensure you have an Angular project set up. If not, create a new Angular project using the following command:

ng new angular-caching
1ng new angular-caching

Navigate to your project directory and open it in your preferred code editor. For this tutorial, we'll use Visual Studio Code.

Implementing Caching with Services and Observables

First, we'll implement a simple caching mechanism using Angular services and observables. This method is straightforward and perfect for beginners.

Creating a Caching Service

Generate a caching service that will handle the storage and retrieval of cached data:

ng generate service cache
1ng generate service cache

In the generated service, define a private property to store the cache:

private cache = new Map<string, any>();
1private cache = new Map<string, any>();

Create get and set methods to manage the cache:

get(key: string): Observable<any> | null {
  return this.cache.has(key) ? of(this.cache.get(key)) : null;
}
set(key: string, value: any): void {
  this.cache.set(key, value);
}
1get(key: string): Observable<any> | null { 2 return this.cache.has(key) ? of(this.cache.get(key)) : null; 3} 4set(key: string, value: any): void { 5 this.cache.set(key, value); 6}

Using the Caching Service in Data Service

Next, generate a data service that will utilize the caching service:

ng generate service data
1ng generate service data

Inject the HTTP client and caching service into the data service and create a method to fetch data:

@Injectable({
  providedIn: 'root'
})
export class DataService {
  constructor(private http: HttpClient, private cacheService: CacheService) {}
  getData(url: string): Observable<any> {
    const cachedResponse = this.cacheService.get(url);
    if (cachedResponse) {
      return cachedResponse;
    }
    return this.http.get(url).pipe(
      tap(response => this.cacheService.set(url, response))
    );
  }
}
1@Injectable({ 2 providedIn: 'root' 3}) 4export class DataService { 5 constructor(private http: HttpClient, private cacheService: CacheService) {} 6 getData(url: string): Observable<any> { 7 const cachedResponse = this.cacheService.get(url); 8 if (cachedResponse) { 9 return cachedResponse; 10 } 11 return this.http.get(url).pipe( 12 tap(response => this.cacheService.set(url, response)) 13 ); 14 } 15} 16

Displaying Data in a Component

Generate a component to display the cached data:

ng generate component articles
1ng generate component articles

In the component, inject the data service and load data using the caching mechanism:

export class ArticlesComponent implements OnInit {
  articles: any[] = [];
  constructor(private dataService: DataService) {}
  ngOnInit() {
    this.loadArticles();
  }
  loadArticles() {
    this.dataService.getData('https://api.example.com/articles')
      .subscribe(data => this.articles = data);
  }
}
1export class ArticlesComponent implements OnInit { 2 articles: any[] = []; 3 constructor(private dataService: DataService) {} 4 ngOnInit() { 5 this.loadArticles(); 6 } 7 loadArticles() { 8 this.dataService.getData('https://api.example.com/articles') 9 .subscribe(data => this.articles = data); 10 } 11} 12

Importing ArticlesComponent in app.component.ts

Now to view this component in browser, we need to add it in app.component.html. But before that we need to import the ArticlesComponent in the imports array of app.component.ts. Please note that I am using Angular 17 standalone components in this tutorial hence you can directly import the ArticlesComponent in app.component.ts instead of declaring in any module.

Now add the <app-articles><app-articles> element in the app.component.html.

Extending Caching to Handle Query Parameters and Request Bodies

For more dynamic API requests, extend the caching service to handle query parameters and request bodies.

Generating Unique Cache Keys

Create a method to generate unique cache keys:

private generateCacheKey(url: string, params: any, body: any): string {
  return `${url}|${JSON.stringify(params)}|${JSON.stringify(body)}`;
}
1private generateCacheKey(url: string, params: any, body: any): string { 2 return `${url}|${JSON.stringify(params)}|${JSON.stringify(body)}`; 3}

Updating Get and Set Methods

Update the get and set methods to use the unique cache keys:

get(url: string, params: any = {}, body: any = {}): Observable<any> | null {
  const key = this.generateCacheKey(url, params, body);
  return this.cache.has(key) ? of(this.cache.get(key)) : null;
}
set(url: string, params: any = {}, body: any = {}, value: any): void {
  const key = this.generateCacheKey(url, params, body);
  this.cache.set(key, value);
}
1get(url: string, params: any = {}, body: any = {}): Observable<any> | null { 2 const key = this.generateCacheKey(url, params, body); 3 return this.cache.has(key) ? of(this.cache.get(key)) : null; 4} 5set(url: string, params: any = {}, body: any = {}, value: any): void { 6 const key = this.generateCacheKey(url, params, body); 7 this.cache.set(key, value); 8} 9

Implementing Caching with HTTP Interceptors

For a more global caching approach, use HTTP interceptors to cache all HTTP requests.

Creating a Cache Interceptor

Generate a cache interceptor:

ng generate interceptor cache
1ng generate interceptor cache

Implement the caching logic in the interceptor:

@Injectable()
export class CacheInterceptor implements HttpInterceptor {
  private cache = new Map<string, any>();
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (req.method !== 'GET') {
      return next.handle(req);
    }
    const cachedResponse = this.cache.get(req.urlWithParams);
    if (cachedResponse) {
      return of(cachedResponse);
    }
    return next.handle(req).pipe(
      tap(event => {
        if (event instanceof HttpResponse) {
          this.cache.set(req.urlWithParams, event);
        }
      })
    );
  }
}
1@Injectable() 2export class CacheInterceptor implements HttpInterceptor { 3 private cache = new Map<string, any>(); 4 intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { 5 if (req.method !== 'GET') { 6 return next.handle(req); 7 } 8 const cachedResponse = this.cache.get(req.urlWithParams); 9 if (cachedResponse) { 10 return of(cachedResponse); 11 } 12 return next.handle(req).pipe( 13 tap(event => { 14 if (event instanceof HttpResponse) { 15 this.cache.set(req.urlWithParams, event); 16 } 17 }) 18 ); 19 } 20} 21

Registering the Interceptor

Register the interceptor globally in your Angular application:

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: CacheInterceptor, multi: true }
]
1providers: [ 2 { provide: HTTP_INTERCEPTORS, useClass: CacheInterceptor, multi: true } 3]

Using Local Storage for Persistent Caching

To preserve the cache across page reloads, use local storage.

Storing and Retrieving Cache from Local Storage

Modify the caching service to use local storage:

set(key: string, value: any): void {
  this.cache.set(key, value);
  localStorage.setItem('cache', JSON.stringify(Array.from(this.cache.entries())));
}
get(key: string): Observable<any> | null {
  if (this.cache.has(key)) {
    return of(this.cache.get(key));
  }
  const storedCache = JSON.parse(localStorage.getItem('cache') || '[]');
  this.cache = new Map(storedCache);
  return this.cache.has(key) ? of(this.cache.get(key)) : null;
}
1set(key: string, value: any): void { 2 this.cache.set(key, value); 3 localStorage.setItem('cache', JSON.stringify(Array.from(this.cache.entries()))); 4} 5get(key: string): Observable<any> | null { 6 if (this.cache.has(key)) { 7 return of(this.cache.get(key)); 8 } 9 const storedCache = JSON.parse(localStorage.getItem('cache') || '[]'); 10 this.cache = new Map(storedCache); 11 return this.cache.has(key) ? of(this.cache.get(key)) : null; 12} 13

Conclusion

Caching is an essential technique to optimize the performance of your Angular applications. By leveraging services, observables, interceptors, and local storage, you can efficiently manage and retrieve cached data. Implementing these methods will not only enhance your application's speed but also reduce unnecessary HTTP requests.

We hope you found this guide valuable and practical. If you enjoyed this post, please like, share, and subscribe to stay updated with the latest content.