Angular: HttpInterceptor declared in library module intercepting requests outside of library

1.7k views Asked by At

I have an issue in Angular whereby an implemented instance of HttpInterceptors declared in an Angular library is intercepting requests for HttpClient calls made outside the library (i.e. the consuming application).

I am struggling to understand why this is happening.

The HTTP_INTERCEPTOR injection is configured on the module declaration for my library, not my application.

My setup is as follows:

I have the ng-mfe library module declared as follows:

import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { ModuleWithProviders, NgModule } from '@angular/core';

import { CommonModule } from '@angular/common';
import { ConfigToken } from './tokens';
import { AuthHttpClient } from './services/auth/auth-http.service';
import { Config } from './models/module.model';
import { NgSelectModule } from '@ng-select/ng-select';
import { TokenInterceptor } from './services/auth/token.interceptor';

const components = [/* components */];
const imports = [CommonModule, HttpClientModule, NgSelectModule, FormsModule, ReactiveFormsModule];

@NgModule({
  declarations: [...components],
  imports: [...imports],
  exports: [...components],
  providers: [],
})
export class NGMfeModule {
  static forRoot(config: Config): ModuleWithProviders<NGMfeModule> {
    return {
      ngModule: NGMfeModule,
      providers: [
        AuthHttpClient,
        TokenInterceptor,
        {
          provide: ConfigToken,
          useValue: config,
        },
        {
          provide: HTTP_INTERCEPTORS,
          useClass: TokenInterceptor,
          multi: true,
        },
      ],
    };
  }
}

This library handles HTTP requests for an API and appends the relevant auth headers required to communicate with said API.

I have implemented a HttpInterceptor (TokenInterceptor) and provided the HTTP_INTERCEPTORS injection token.

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { filter, map, switchMap } from 'rxjs/operators';
import { selectAccessToken } '../../state/shared-config/shared-config.selectors';

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
  constructor(private store: Store) {}

  /**
   * Intercept all HTTP requests and add the required auth headers to them
   *
   * @param req The request to intercept
   * @param next Transform HttpRequests into a stream
   */
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return this.store.select(selectAccessToken).pipe(
      filter((data) => !!data),
      switchMap((data) => {
        req = req.clone({
          setHeaders: {
            Authorization: `Bearer ${data}`,
          },
        });

        return next.handle(req);
      })
    );
  }
}

And finally, my AuthHttpClient:

import { HttpClient } from '@angular/common/http';
import { HttpOptions } from '../../models/http.model';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable()
export class AuthHttpClient {
  constructor(private http: HttpClient) {}

  get(url: string, options?: HttpOptions): Observable<Object> {
    return this.http.get(url, options);
  }
}

This is the library configuration, and my Angular application's module outside of the library is as follows:

import { NGMfeModule } from 'ng-mfe';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    NGMfeModule.forRoot(/* config */),
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

And finally, in my AppComponent class, I have injected two services, one being my AuthHttpClient and the other being Angular's HttpClient:

import { Component, OnInit } from '@angular/core';
import { AuthHttpClient, getAccessToken } from 'ng-mfe';

import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
  constructor(
    private http1: AuthHttpClient,
    private http2: HttpClient
  ) {}

  ngOnInit() {
    this.http1.get('test-url-1').subscribe();
    this.http2.get('test-url-2').subscribe();
  }
}

I would expect the call made by http1 to be intercepted since that is an instance of AuthHttpClient which is declared in ng-mfe, but http2 is an instance of Angular's HttpClient - and the HttpClientModule is imported in AppModule.

Maybe my understanding of Angular's singletons are wrong - but why are requests from http2 being intercepted? Is my setup wrong (and is there a way to correctly configure my modules so this does not happen)? Or is this correct, and standard behaviour?

I previously had Injectable({ providedIn: 'root' }) in my AuthHttpClient and TokenInterceptor service without them declared in the providers array in NGMfeModule but removed these so they are declared locally. But it has not made any difference. Is this correct, or should they always be providedIn: root?

Thanks for the help!

1

There are 1 answers

0
alexnavratil On BEST ANSWER

There are two solutions to your problem:

  1. You could provide your own HttpClient implementation and inject your HTTP Interceptor there, as it is shown here: https://indepth.dev/posts/1455/how-to-split-http-interceptors-between-multiple-backends

  2. With Angular 12 there are now HttpContextTokens. With these you can conditionally apply your logic in your HTTP Interceptor based on the existence of a specific token in the request: https://netbasal.com/new-in-angular-v12-passing-context-to-http-interceptors-308a1ca2f3dd