HTTP Interceptor send same request twice
Asked Answered
T

2

6

I'm using an HTTP interceptor in order to add auth token to requests but when the http client fires the request, this is intercepted and sent twice

This is my HttpClient call

  searchProd(query: string, limit?: number): Observable<Product[]> {
    let _limit = limit || 5;
    return this.http.get<Product[]>(`${API_CONST.HOST}/${API_CONST.PRODUCT}?search=${query}&limit=${_limit}`);
  }

This is my app.module.ts

import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; 
import { TokenInterceptor } from './auth/token.interceptor';
....

@NgModule({
  declarations: [
    ...
  ],
  imports: [
    ...
  ],
  providers: [
    ApiService,
    AuthGuardService,
    SettingsService,
    {
      provide : HTTP_INTERCEPTORS,
      useClass : TokenInterceptor,
      multi : true
    }
  ],
  entryComponents: [ ... ],
  bootstrap: [ ... ]
})

This is my token.interceptor.ts

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpResponse,
  HttpErrorResponse
} from '@angular/common/http';
import { AngularFireAuth } from '@angular/fire/auth';
import { Observable } from 'rxjs';
import { AuthGuardService } from './auth-guard.service';
import { API_CONST } from '../services/api/api.service';

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
  private no_auth_endpoints = [
    `${API_CONST.HOST}/${API_CONST.PRODUCT}`
  ]
  private token = null;
  constructor(public af: AngularFireAuth, private authGuard: AuthGuardService) {
    this.token = authGuard.getToken();
  }
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const headersConfig = {
      'Authorization': `Bearer ${this.token}`
    };

    let isAuthEnpoint = true;
    this.no_auth_endpoints.forEach(endpoint => {
      if(request.url.includes(endpoint))
        isAuthEnpoint = false;
    })

    if (!request.headers.has('Authorization') && isAuthEnpoint) {
      const modified = request.clone({
        setHeaders : headersConfig
      });

      return next.handle(modified); //this line is executed twice!
    }
    else {
      return next.handle(request); //this line is executed twice!
    }

  }
}

Throught the chrome dev tools i see the same request sent twice in the network tab. During debugging I saw the http request send by searchProd once but when it's intecepted the next.handle() is executed twice. How to fix that in order to send only one request?

EDIT: This is what is shown in network tab

First request Request 1

Second request Request 2

EDIT2: This is the code where I call the searchProd(string) function.

component.html

<mat-form-field class="bottom-search-field">
    <input [formControl]="autoCompleteControl" type="text" placeholder="Aggiungi un prodotto"
      matInput [matAutocomplete]="auto">
    <mat-autocomplete autoActiveFirstOption #auto="matAutocomplete" (optionSelected)="onSelectionChanged($event)">
      <mat-option *ngFor="let item of searchResults | async; let index = index" [value]="item.description | titlecase">
        {{ item.description | titlecase}}
      </mat-option>
    </mat-autocomplete>
  </mat-form-field>

component.ts

public autoCompleteControl = new FormControl();
...
ngOnInit(): void {
    this.searchResults = this.autoCompleteControl.valueChanges.pipe(
      startWith(''),
      switchMap(value => {
        if (value.length > 3) {
          let prodSearched = this.apiService.searchProd(value);
          prodSearched.pipe(share()); // ADDED PIPE SHARE
          this.saveProdSearched(prodSearched);
          return prodSearched;
        } else {
          return of(null);
        }
      })
    );
  }
  //This function save the last result inside of an array of Product
  private saveProdSearched(prodSearched: Observable<Product[]>) {
    prodSearched.subscribe(items => {
      this.lastSearch = items
    })
  }
Treatise answered 4/5, 2019 at 11:28 Comment(19)
I very much doubt the same request is sent twice. My guess is that you're using CORS, and that you are thus seeing the standard pre-fetch OPTIONS request followed by the actual GET request.Antimonic
Please check the headers section in the network tab and verify whether the Request Method is Get in both the casesInsolence
@JBNizet the backend is in NodeJS where I use a cors package. Without it I can't fetch my apis so is it possible to avoid this issue with cors bypass active on server side?Treatise
mind adding the code in which you call searchProd?Kennie
@FranklinPious both request methods are GETTreatise
Then it means the calling code is sending the request twice, probably by subscribing twice to your observable.Antimonic
@Kennie see edit 2Treatise
@JBNizet may you find out where this happens looking at the code in the post? Thanks for your helpTreatise
My guess is that you are using the async pipe on searchResults in two different places in your template. Try adding the shareReplay(1) operator call into your stream pipeline.Kennie
Please add the sections of your template where you bind searchResultsKennie
@Kennie done, see component.html in edit 2. Thanks for helpTreatise
Are you binding searchResults | async to some variable anywhere else in the template? Are you manually subscribing searchResults in your component? Did shareReplay(1) had any effect? The interceptor code is fine, your stream pipe is fine. The valueChanges stream is correctly emitting single values. If it wasnt the case, all requests other than the last would be canceled because of the switchMap. So I agree with @JBNizetKennie
You might want to add a distinctUntilChanged operator, at least to see if the valueChanges doesn't emit the same value twiceAntimonic
@Kennie the problem is with the function call this.saveProdSearched(prodSearched) inside the component.ts block. Because I subscribe to the prodSearched observable and so I fire another request but I don't know why. I have to mantain this behaviour in order to save the results for other purposes. How can I obtain the same without subscribing/launching the request again? I tried with prodSearched.pipe(share()) before passing it to the function but nothing changed. I provide the code inside the edit2.Treatise
@JBNizet you was right! I subscribe inside the template and also inside the component.ts, but I still need these two subscription in order to save the result for other purposes, how can I avoid to send twice the request and maintain the current behaviour?Treatise
The easiest way is probably to not subscribe in the template: since the result is saved in the component, just display the result stored in the component.Antimonic
@JBNizet thank you very much! I solved the issue, you saved meTreatise
simply add shareReplay(1) at the end of your pipe callKennie
@Kennie I solved following JBNizet instruction, anyway thank you for your help and time spent on my issue!Treatise
T
2

The problem was that I was subscribing twice. One in the function this.saveProdSearched(prodSearched); and one in the template with pipe async. I solved the issue simply saving the result of the saveProdSearched() function, removing the async pipe from template and displaying the result from an array of Product

Treatise answered 4/5, 2019 at 16:56 Comment(0)
B
0

it happened to me too, but in my case i'm using async pipe on a same Observable returned by HttpClient on 2 different components's templates, so it's subscribing to the Observable twice, that's why it's intercepting the request twice in the HttpInterceptor.

You need to check if you're using async or .subscribe() on the same Observable 2 times (the Observable returned by HttpClient)

Bobsledding answered 27/9, 2021 at 12:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.