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!
There are two solutions to your problem:
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-backendsWith 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