Implementing an Angular HttpInterceptor to improve caching efficiency for simultaneous requests by utilizing a shared observable

I am looking to implement caching for HTTP parallel requests by sharing the observable and also storing the response in a Map object.

Check out the online demo

caching-interceptor.service.ts

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

    @Injectable()
    export class CachingInterceptorService implements HttpInterceptor {

      public readonly store = new Map<string, HttpResponse<any>>();
      public readonly queue = new Map<string, Observable<HttpEvent<any>>>();

      constructor() {}

      intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
          
        // Don't cache if it's not cacheable
        if (req.method !== 'GET') {
          return next.handle(req);
        }

        // Check if there is a pending response for this request
        const cachedObservable: Observable<HttpEvent<any>> = this.queue.get(req.urlWithParams);
        if (cachedObservable) {
          console.info('Observable cached');
          return cachedObservable;
        }

        // Check if there is a cached response for this request
        const cachedResponse: HttpResponse<any> = this.store.get(req.urlWithParams);
        if (cachedResponse) {
          console.info('Response cached');
          return of(cachedResponse.clone());
        }

        // If the request is going through for the first time,
        // let the request proceed and cache the response
        console.info('Request execute');
        const shared = next.handle(req).pipe(
          tap(event => {
            if (event instanceof HttpResponse) {
              console.info('Response reached');
              this.store.set(req.urlWithParams, event.clone());
            }
          }),
          finalize(() => {
            // Delete pending request
            this.queue.delete(req.urlWithParams);
          }),
          share()
        );

        // Add pending request to queue for caching parallel requests
        this.queue.set(req.urlWithParams, shared);

        return shared;
      }
    }
  

Is this implementation of observable caching correct?

I have some doubts about what happens if the observable is deleted during the finalization of the request and someone has subscribed to it.

Side Note: This is just an example and does not include cache expiring/invalidation logic.

Answer №1

Your solution seems to be functioning correctly, although I believe there is room for simplification.

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { shareReplay, first, filter } from 'rxjs/operators';


@Injectable()
export class CachingInterceptorService implements HttpInterceptor {

  public readonly cache: Record<string, Observable<HttpEvent<any>>> = {};

  constructor() {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    if (req.method !== 'GET') {
      return next.handle(req);
    }

    const cachedResponse = this.cache[req.urlWithParams] ||
      (this.cache[req.urlWithParams] = next.handle(req).pipe(
          filter((res) => res instanceof HttpResponse ),
          shareReplay(1),
      ));

    return cachedResponse.pipe(first());
  }
}

View my modified implementation on Stackblitz

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Guide to successfully verifying the correct value in ReactJS unit tests

I recently designed a reactjs component with an onClick event that modifies text within the component. Here is the code snippet for the component: import React, {Component} from 'react' export default class SimpleComponent extends Component{ c ...

Step by step guide on rotating a plane with texture by 90 degrees

I've been working on developing an fps game, but I'm encountering an issue where the floor disappears from the scene when I try to rotate it close to 90 degrees. Here's the code snippet responsible for creating the plane. var colorMap = new ...

What is the best way to efficiently handle onChange events for multiple input checkboxes in Reactjs?

When I attempt to assign an onChange event listener to a group of checkboxes, clicking on one checkbox results in all checkboxes being clicked and the conditional inline styles that I defined are applied to all of them. Here is the JSX code snippet: class ...

SyntaxError: Encountered an unexpected token that is not jsonp, could it be trying to parse json instead?

As a newcomer to AJAX and Javascript, I am attempting to integrate them with an API following this structure: http://localhost:8088/JobPositionForDd: { "data": [{ "_id": "529dc2dfd0bf07a41b000048", "name": "Junior Android" }, { ...

Tips for refreshing an html table without affecting the scroll location?

HTML: <div class="html_table"></div> # Within the html body tag. Utilizing Ajax function to retrieve table data. var $html_table= $('.html_table'); function ajaxCallFunction() { $.ajax({ type: 'POST', ...

How can I modify the join() function of an Array<MyType> in Typescript to instead return MyType instead of a string?

I am working with a specialized string type called MyType = string & { __brand: 'mytype' }. Is there a way to define an override for the Array.join method specifically for arrays of type Array<MyType> so that it returns MyType instead of s ...

Display a helpful tooltip when hovering over elements with the use of d3-tip.js

Is it possible to display a tooltip when hovering over existing SVG elements? In this example, the elements are created during data binding. However, in my case, the circles already exist in the DOM and I need to select them right after selectedElms.enter ...

Position the label and the select dropdown side by side within the sweetalert 2 component

Can anyone provide an example of aligning a label and dropdown in the same row using sweetalert2? I attempted to customize the label placement, but it appears on a separate line. I would like to log the selected dropdown item upon clicking the OK button. ...

Pug: perform a task depending on the presence of an element within a variable

I'm currently working with Express js to create a web application. I make use of an API to fetch some data, which is then sent to a pug file in the following format. res.render('native.pug', {product_name: body.products, cart_items:body.car ...

Sending a POST request in my Angular 11 application is no problem, but I am curious about how to retrieve data specifically for a certain input value

question-task.image **verify if the image question is included** <form #postForm="ngForm" (ngSubmit)="onCreatePost(postForm.value)"> <div class="form-group"> <label for="title">Ti ...

Troubleshooting: AngularJS ng-include not functioning as expected

I attempted to replicate the steps outlined in an Angular template at . Unfortunately, something went wrong and I am unable to identify the issue. Here is what the code looks like: menu.html <div class="container"> <div class="row row-conte ...

Managing JSON object with irregular data in Angular 7: Best Practices

When the service returns data in a specific format, I am able to view the data in the developer tools. {"results":{"BindGridDatatable":[{"ID":"0005","Name":"Rohit"}, {"ID":"0006","Name":"Rahul"}], "Totalvalue":119}} ...

A function designed to detect errors based on the parameters supplied as arguments

In order to ensure secure access to my restful API, I am looking to implement an authentication function called "access". I would like the function to have the following structure whenever a user interacts with the server: access(id , token ,function(err) ...

What is the method for retrieving JSON data within the AngularJS repeat directive?

I am seeking guidance on how to retrieve the main menu and sub menus separately from a JSON file using ng-repeat. Here are the relevant files: menu.json {"data": { "main menu": { "menu1": [ ...

Prevent rendering a file in node.js using ejs if it cannot be found

I have a specific folder structure under the views directory, containing an EJS file named profile_60113.ejs views docs profile_60113.ejs To dynamically render the file based on the groupID (where data.groupID == 60113), I use the following c ...

React modal image showing a misaligned image upon clicking

I recently integrated the react-modal-image library into my project to display images in a modal when clicked. However, I encountered an issue where the displayed image is off center with most of it appearing offscreen. I'm unsure what is causing this ...

Intro.js is not compatible with React and Remix.run

I am currently working on implementing onboarding modals for header links using intro.js within a React environment. Below is the code snippet: import { useState, type FC } from 'react' import type { Links } from '../types' import &apo ...

JavaScript transform numbers into array of buffers with a length of 4

I'm looking for a way to insert a numerical value into an array of 4 values [uint32_t] For example 255 should look like [0x00, 0x00, 0x00, 0xFF] I need to transmit this value from a Node.js server to an Arduino board Is there a built-in solution or ...

next-redux-wrapper is being invoked repeatedly and experiencing multiple calls to HYDRATE

I'm embarking on a new Next.js project, transitioning from a standard React app to a Next.js application. Our plan is to utilize Redux Toolkit for global state management and incorporate server-side rendering. During this process, we discovered the ne ...

Is there a way to verify if the object's ID within an array matches?

I am looking to compare the ID of an object with all IDs of the objects in an array. There is a button that allows me to add a dish to the orders array. If the dish does not already exist in the array, it gets added. However, if the dish already exists, I ...