Angular interceptors in sequence

I'm looking to implement a queue system in Angular. My goal is to store requests in an array and process them sequentially, moving on to the next request once the current one is successful.

Below is the code I tried, but unfortunately it didn't work as expected:

private requestQueue: HttpRequest<any>[] = [];
  isQueueActive: boolean = false;
  intercept(request: HttpRequest<any>, next: HttpHandler): any {
 
      this.requestQueue.push(request);
      if (!this.isQueueActive) {
        this.isQueueActive = true;
        return this.processQueue(next);
 }
      return new Observable<HttpEvent<any>>();
    } 


  private processQueue(next: HttpHandler): Observable<HttpEvent<any>> {
    const request = this.requestQueue.shift(); 

    if (request) {
      return next.handle(request).pipe(
        tap((data: any) => {
          this.processQueue(next);
        }),
      ); 
    } else {
      this.isQueueActive = false;
      return new Observable<HttpEvent<any>>(); 
    }
  }

I've shared my code here, but I'm open to other suggestions such as npm packages. Any help would be greatly appreciated :)

Answer №1

It seems that your struggle lies in attempting to blend imperative and reactive approaches. It is often more effective to stick with just one approach rather than combining both. When dealing with complex asynchronous operations, following a reactive programming paradigm usually yields better results.

Here is an alternative approach:

@Injectable()
export class QueueRequestsInterceptor implements HttpInterceptor {
  private intercepted$$ = new Subject<Intercepted>();

  private queue$ = this.intercepted$$.pipe(
    concatMap(({ request, next }) =>
      next.handle(request).pipe(
        materialize(),
        map((materializedResponse) => ({
          request,
          materializedResponse,
        }))
      )
    ),
    share()
  );

  public intercept(
    request: HttpRequest<unknown>,
    next: HttpHandler
  ): Observable<HttpEvent<unknown>> {
    return merge(
      this.queue$.pipe(
        filter((res) => res.request === request),
        first(),
        map(({ materializedResponse }) => materializedResponse),
        dematerialize()
      ),
      defer(() => {
        this.intercepted$$.next({ request, next });
        return EMPTY;
      })
    );
  }
}

To start off, I create a subject where requests are queued up.

Then, the queue is established using concatMap to ensure that requests are executed sequentially. If a request fails, we use materialize to prevent the entire queue from failing. This function wraps the observable result into an object, ensuring that it does not throw any errors. We then use map to retain both the request (for filtering later on) and the materialized response. Finally, we use share to prevent multiple subscriptions to the queue for each request.

In the intercept method, we revisit the merge and defer functions. We listen to the queue and filter for the current request only. By using first, we avoid potential memory leaks by keeping the observable open. In case of a failed HTTP call, we revert to the original behavior using dematerialize. This ensures that the app receives the appropriate response without disrupting the queue.

If we had utilized the following code snippet:

this.intercepted$$.next({ request, next });

return this.queue$.pipe(
  filter((res) => res.request === request),
  map(({ materializedResponse }) => materializedResponse),
  dematerialize()
);

While this might work in most scenarios, there could be cases where requests are resolved immediately or fail instantly. To cover all possibilities, I utilized a merge to begin listening to the queue first before subscribing to the defer, triggering the side effect of adding the current request to the queue.

I have created a live demo showcasing a second interceptor for mocking HTTP responses.

A 3-second delay was imposed on each request, resulting in the following output:

Initially, we observe:

Putting request http://mocked-request-anyway/0 in queue
Putting request http://mocked-request-anyway/1 in queue

+3s:

Received response for request http://mocked-request-anyway/0
{count: 1}

+3s:

Received response for request http://mocked-request-anyway/1
{count: 2}

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

Is the Prisma model not reachable through Prisma Client?

I'm currently attempting to retrieve a specific property of a Prisma model using Prisma Client. The model in question is related to restaurants and includes a reviews property that also corresponds with a separate Review model. schema.prisma file: // ...

Combining TypeScript with Vue3 to implement bootstrapVue

After using BootstrapVue as any, the error was corrected but unfortunately it still doesn't work in the browser. Here is what's inside main.ts: import { createApp }from 'vue'; import App from './App.vue'; import router from & ...

How to Extract the Specific Parameter Type from a Function in Typescript

After generating a client for an API using typescript-node, I encountered the following code: export declare class Api { getUser(username: string, email: string, idType: '1298' | '2309' | '7801') } I need to access the ...

Unable to update the value of the p-editor component in PrimeNG

After creating a form with the p-editor element for rich formatted text entry, a problem arose when attempting to update the form with server values using Angular's reactive form patchValue method. The editor does not update as expected with the new v ...

I'm having trouble with my code not working for get, set, and post requests. What could be causing this issue and how can I

Here are the TypeScript codes I've written to retrieve product details and delete them. import { Component, OnInit } from '@angular/core'; import {FormGroup,FormBuilder, FormControl, Validators} from "@angular/forms" // other impor ...

Ways to mandate a field to only be of type object in TypeScript

I need to design a type that includes one mandatory property and allows for any additional properties. For instance, I require all objects to have an _id property of type string. {_id: "123"} // This will meet the criteria {a: 1} // This will not work as i ...

Retrieve information from a JSON response that is in comma-separated format and adjust the button to be either enabled or disabled depending on the value in IONIC

I received a JSON response with one parameter containing a comma-separated string as shown below: this.dataList = [{ "id":1, "name": "Ram", "total_slots": 3, "alloted_slot":"a1,a2,a3" }, { "id":2, "name": "As ...

Requires the refreshing of an Angular component without altering any @Input properties

Currently delving into the world of Angular (along with Typescript). I've put together a small application consisting of two components. This app is designed to help track work hours (yes, I am aware there are commercial products available for this pu ...

Is it possible to inject a descendant component's ancestor of the same type using

When working with Angular's dependency injection, it is possible to inject any ancestor component. For example: @Component({ ... }) export class MyComponent { constructor(_parent: AppComponent) {} } However, in my particular scenario, I am tryin ...

Angular displaying a slice of the data array

After following the example mentioned here, and successfully receiving API data, I am facing an issue where only one field from the data array is displayed in the TypeScript component's HTML element. Below is the content of todo.component.ts file im ...

The Angular frontend application with proxy configuration is sending requests to the incorrect backend URL

My application is using Angular 11.0.6 as the front end, deployed on IIS and configured for mywebsite.com (port 80). The backend consists of a dotnet core web API deployed on IIS and configured for my.server.ip.address:190. Both the front end and back end ...

While developing an exam portal with Angular and Spring Boot, I encountered an issue when trying to incorporate a name field as [name]

Component.html <div class="bootstrap-wrapper" *ngIf="!isSubmit"> <div class="container-fluid"> <div class="row"> <div class="col-md-2"> <!- ...

Using React, TypeScript, and Next.js to transform all elements in a static array to their last occurrence

I'm having trouble updating my array. Every time I click the button for the second time, only two or more records are added, similar to the last one I added. Does anyone know how to fix this issue? In the images below, you can see the results of the ...

Tips for setting variable values in Angular 7

I'm encountering an issue with assigning values to variables in my code. Can anyone provide assistance in finding a solution? Here is the snippet of my code: app.component.ts: public power:any; public ice:any; public cake:any; changeValue(prop, ...

Next.js API routes encountering 404 error

I am encountering an issue with calling my route API (404) in my new nextjs project. The route API can be found at src/app/api/speed.js Within my page src/app/page.tsx, I have the following code snippet: fetch("api/speed").then(res=>res.json ...

Looking to update properties for elements within the Angular Material 16 mat-menu across the board?

Currently working with Angular Material 16 and I have a question regarding the height of buttons inside the mat-menu element. Here is an example of the code: <button mat-icon-button> <mat-icon>more_vert</mat-icon> </button> ...

Remove all `Function.prototype` methods from Typescript

Can a callable object (function) be created without utilizing Function.prototype methods? let callableObject = () => 'foo' callableObject.bar = 'baz' callableObject() // 'foo' callableObject // {bar: 'baz'} call ...

Access-Control-Allow-Methods does not allow the use of Method PUT in the preflight response, as stated by Firebase Cloud Functions

I am facing an issue with my Firebase cloud function endpoint. I have a setup where it forwards PUT requests to another API endpoint. I have configured the following Access-Control-Allow- headers: // src/middlewares/enableCORS.ts export default function en ...

Error message: The ofType method from Angular Redux was not found

Recently, I came across an old tutorial on Redux-Firebase-Angular Authentication. In the tutorial, there is a confusing function that caught my attention: The code snippet in question involves importing Actions from @ngrx/effects and other dependencies to ...

Encountering a syntax issue with pipeable operators in Angular Rxjs

I am currently in the process of rewriting this code snippet: Observable .merge(this.searchQuery$, this.lazyQuery$) .do(() => this.loadingPage()) .map(filter => this.buildURL("jaume", Config.security['appName'], filter)) .s ...