6

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.

  • 1
    just implement a check inside the interceptor and skip it by calling `next()` – Max Koretskyi Oct 15 '17 at 05:33
  • Thanks, but I would use it as the last resort. I don't want to put such dirty logic into the interceptors. I'm wondering if there's any way to inject a clean instance of `HttpClient`, apart from the one with interceptors, if I need. Or I can use only one global instance of `HttpClient`? –  Oct 15 '17 at 05:44
  • this is not a dirty logic, this is how middleware pattern work in JavaScript. yes, usually there's only one instance of `HttClient` registered by `HttClientModule`. But probably you can define more instances by registering a HttClient as a provider on child lazy loaded modules or components – Max Koretskyi Oct 15 '17 at 05:45
  • If I implement an authentication interceptor, I don't want to keep a list of urls that this interceptor needs to be applied. What if I would share the same interceptor in multiple projects? Would the interceptor keep a list of all urls for different projects? All I need is to be able to inject a clean instance of `HttpClient` as I need. –  Oct 15 '17 at 05:49
  • I don't understand, show the implementation of your interceptro – Max Koretskyi Oct 15 '17 at 05:51
  • The implementation of one of my interceptor is super simple, it just check some fields in the http response header, and to modify the response body according to the check result of the header. I don't want the interceptor to know who are going to use it. I want the one who needs to use the interceptor use it. –  Oct 15 '17 at 05:57
  • then just make the interceptor configurable, pass a function `shouldApply` to it when registering and an interceptor should call that function passing the http response/request to it. if the function returns true, the interceptor should perform some logic. in this way you decouple checking condition from the interceptor logic – Max Koretskyi Oct 15 '17 at 06:01
  • Oh no. I think I misunderstood what you said. I still don't like this way the interceptors need to call something like `shouldApply`. This is ugly. I'm stilling thinking of multiple instance of `HttpClient`. But it looks like the Angular 4 is designed to have interceptors to be applied to the HttpClient class level, as opposed to instance level. If this is the case, it's sad. –  Oct 15 '17 at 06:13
  • All I need is like what is discussed here: https://github.com/axios/axios/issues/108. If Angular 4 is not designed for different instances of `HttpClient` to have their respective interceptors. Now I'm wondering why I have to use the Angular 4 built-in `HttpClient` at all. Why not just use Axios which implemented what I need nicely? I know the built-in one has some tricks about Rxjs async, but that is not really that important to me, maybe important to others. I don't know. –  Oct 15 '17 at 06:19
  • different instances of HttpClient won't make a difference because every HttpClient instance injects interceptors by the same provider name `HTTP_INTERCEPTORS`. And you can't have several stacks of interceptors under the same name. Your frustration comes from the fact that you probably haven't worked with middleware pattern before. Angular just chose to use this pattern. It's not better or worse, it's just different – Max Koretskyi Oct 15 '17 at 06:27
  • Oh no. I am a backend developer and I designed middleware and interceptor frameworks. I clearly know what interceptors, and dependency inversion is. But don't you think making it the same provider name `HTTP_INTERCEPTORS` an improvable (read as poor) design? –  Oct 15 '17 at 06:31
  • Back then in the Tomcat server, even each servlet can have its own chain of filters. Each chain of filters is configured at the instance level of servlet. That's intuitive design. I don't have rights to judge Angular design. Maybe they don't think it's necessary. But clearly it's not something impossible. And the way I wanted doesn't hinder anything in anyway for their current design. Is it correct? –  Oct 15 '17 at 06:35
  • I was talking more of a functional way middleware, ExpressJS like middleware. Anyways, it's designed the way it's designed, we just have to learn and use it. As I said, it's just a different paradigm. When I was learning Node.js backend it was very different from OOP like back-end designs I got used to. – Max Koretskyi Oct 15 '17 at 06:43
  • Typescript which Angular 4 adopted tried to make Javascript looks like an OOP like Java or C#. Now I learned it and I don't think it's what I wanted. I think I would go for Axios. To be fair, Angular 4 is beautiful, apart from the `HttpClient` design, which I think it's ugly. I don't care about the Rxjs async features. What I care is that I can create multiple instances of Axios and attach different sets of interceptors to each of them, or not at all. –  Oct 15 '17 at 06:49
  • To be really fair, I actually care about the Rxjs async features. I love the way Rxjs works. It provided tons of lovable features that Promise doesn't have. The thing is if I have to choose to give up either Rxjs or instance level interceptors, I would choose to give up Rxjs. –  Oct 15 '17 at 07:05
  • Any one have opinions on this method (using HTTPBackend to avoid the interceptor) - https://stackoverflow.com/questions/46469349/how-to-make-an-angular-module-to-ignore-http-interceptor-added-in-a-core-module – Rodney May 21 '18 at 23:21

1 Answers1

3

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.

fasfsfgs
  • 956
  • 10
  • 16
Audaxocles
  • 463
  • 4
  • 10
  • From this problem, including the argument, I suddenly questioned myself, why would I use Anguar at all. It's all about styling, positioning and handling events on boxes/divs. What else? The only useful thing things like Angular provide for me is dynamic binding. But consider the problems it brings, is it really worth using it at all? –  Dec 08 '18 at 09:12
  • This solution (the one we can choose what interceptor we exclude) is awesome! It broke AOT in my app tho. From what I understand, we can't call functions in our decorators with AOT. So I had to change it back to provide a simple object and customizing my exclusions at the function I use at the useFactory level. – fasfsfgs Jan 10 '19 at 21:07
  • I had issues with interceptors from a library project interfering with the interceptors from the main project, and vice versa. I created an additional injection token (LIB_HTTP_INTERCEPTORS) and used the rest of this approach to be able to inject an HttpClient into my library services that only uses the library interceptors. This was a really useful post, thanks! – raymondboswel Dec 04 '20 at 09:46