Prevent API Call Duplication in Angular

An Angular app contains a tree of Angular components. There can be a multitude of separate different components in a single angular application page. Some times these components need real data to show probably from an API. You may choose to make API calls from a component or, better, just present data from a service which makes API calls on the trigger. In both cases, components will ask for new data and trigger an API call. In big applications with many components working together, it is common to have different components showing the same or similar data. The problem is if components trigger the same API call for the same data at the same time, this will waste your server resources tremendously.

In my previous posts HttpClient and HttpInterceptor we introduced some angular concepts and see how powerful angular HTTP tools are. In this article, we will manage client-side micro caching through HTTP interceptors. We will create a simple HTTP interceptor in Angular and cut all requests to the server. When there is an API call, the interceptor saves the call as an active calls cache on the client-side and when the user requests the same URL again and active call exists, it does not go back to the server. Instead, client data is served as the same active call. This implementation ensures that there will only be one API call for the same data at any time while requiring no other change in components and services.

import { Injectable } from '@angular/core';
import { HttpEvent, HttpRequest, HttpHandler, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';
import { share, finalize } from 'rxjs/operators';

@Injectable()
export class CacheInterceptor implements HttpInterceptor {
  private activeCalls = new Map<string, any>();

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const url = req.urlWithParams;
    const method = req.method;
    const activeCall = this.calls.get(url);
    
    if (activeCall) {
      return this.activeCalls[url];
    } else {
      const call = next.handle(req).pipe(
        share(),
        finalize(() => {
          if (this.activeCalls[url]) {
            this.calls.delete(url)
          }
        }));
        if (method === 'GET') {
          this.activeCalls.set(url, call);
        }
        return call;
    }
  }
}