The following url explained how to use http interceptor in Angular 4: https://angular.io/guide/http#intercepting-all-requests-or-responses
However, I'm wondering whether there is any way to choose whether to use the interceptors or not? Or to choose which set of interceptors to use? I want to implement a set of authentication interceptors, however, I don't want these interceptors to be applied when users access things like login
, signup
and forget-password
which do not need user authentication.
All I need is to inject a clean instance of HttpClient
into my service, if I need. I don't like the way that I can only use a single global instance of HttpClient
that is being polluted by all interceptors.
I had this very same requirement and came up with the following solution.
In the Module, I 'provide' an HttpClient using a Token as follows.
export const HTTP_NOAUTH = new InjectionToken("http_noauth");
...
providers: [...,
{
provide: HTTP_NOAUTH,
deps: [HttpBackend],
useFactory: (handler: HttpBackend) => {
return new HttpClient(handler);
}
},
{
provide: HTTP_INTERCEPTORS,
useClass: AuthHttpInterceptor,
multi: true
}],
...
Then, when I am injecting the HttpClient and don't want the AuthHttpInterceptor to be utilized, I specify '@Inject(HTTP_NOAUTH)'.
@Injectable({
providedIn: 'root',
})
export class SomeService {
constructor(@Inject(HTTP_NOAUTH) private http: HttpClient) {
...
The one major hole in this that I've found so far (and there may be more) is that it's an all or nothing solution. It either has all the Interceptors or it has none of them. It may be possible to inject individual Interceptors in the Token provided entry but I haven't dug far enough yet.
UPDATE:
I can now select which interceptors to exclude for each configuration of an HttpClient as follows.
import { Observable } from 'rxjs';
import { HttpHandler, HttpEvent, HttpRequest, HttpInterceptor, HttpBackend, HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
import { Injector, InjectionToken } from '@angular/core';
export const provideTokenizedHttpClient = (token: InjectionToken<string>, options: { excludes: Function[] } = { excludes: [] }) => {
return {
provide: token,
deps: [HttpBackend, Injector],
useFactory: (backend: HttpBackend, injector: Injector) => {
return new HttpClient(
new HttpDynamicInterceptingHandler(backend, injector, options)
);
}
}
}
class HttpInterceptorHandler implements HttpHandler {
constructor(private next: HttpHandler, private interceptor: HttpInterceptor) { }
handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
return this.interceptor.intercept(req, this.next);
}
}
class HttpDynamicInterceptingHandler implements HttpHandler {
private chain: any = null;
constructor(private backend: HttpBackend, private injector: Injector, private options: { excludes: Function[] } = { excludes: [] }) { }
public handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
if (this.chain === null) {
const interceptors = this.injector.get(HTTP_INTERCEPTORS, [])
.filter(entry => !this.options.excludes.includes(entry.constructor));
this.chain = interceptors.reduceRight((next, interceptor) => {
return new HttpInterceptorHandler(next, interceptor);
}, this.backend);
}
return this.chain.handle(req);
}
}
And now in my providers I simply use the following:
providers: [...
provideTokenizedHttpClient(HTTP_NOAUTH, { excludes: [AuthHttpInterceptor] }),
{
provide: HTTP_INTERCEPTORS,
useClass: AppBusyHttpInterceptor,
multi: true
},
{
provide: HTTP_INTERCEPTORS,
useClass: AuthHttpInterceptor,
multi: true
}],
The creation of the InjectionToken and its usage on the @Inject decorator is the same.
I had similar problem and I spend a lot of time to resolve it. I wanted to make interceptors layer independent from interceptors apply (or not apply) logic.
My solutions was to make separated module dedicated only for interceptors. None of interceptors includes if
-conditions to decide if should fire or not. I moved this logic to the forRoot
method, which is dedicated to pass additional configuration to the module.
Example call:
InterceptorModule.forRoot([
{
provide: HTTP_INTERCEPTORS,
useClass: MenuInterceptor,
multi: true,
runConditions: [InterceptConditions.WhiteList],
whiteList: ['api/menu']
},
{
provide: HTTP_INTERCEPTORS,
useClass: AuthorizationInterceptor,
multi: true,
runConditions: [InterceptConditions.BlackList],
blackList: ['api/public']
},
]),
It's all. Interceptors contains pure logic and forRoot
method contains run conditions.
To make it work you have to:
- Add additional abstract class with encapsulated logic to support black/white lists. Every interceptor has to extend it.
- Overload original
ClassProvider
interface with additional fields
example:
export interface InterceptorClassProvider extends ClassProvider {
runConditions: InterceptConditions[];
whiteList?: string[];
blackList?: string[];
}
- load everyhing in
forRoot
method
example:
static forRoot(interceptorProviders: InterceptorClassProvider[]): ModuleWithProviders {
return {
ngModule: CwaCoreInterceptorModule,
providers: [
{ provide: INTERCEPTOR_CONFIG, useValue: interceptorProviders },
...interceptorProviders
],
};
}