DEV Community

Abou Kone
Abou Kone

Posted on

Angular: When HttpInterceptor doesn’t work with lazy loaded modules


Http Interceptor working with a Lazy Loaded Module

If you’re doing Angular the right way, you should be using some kind of httpinterceptor. An HttpInterceptor provides a standard way to intercept HTTP requests and responses and apply custom transformations as suits your application needs. If you’re not familiar with the different use cases for HttpInterceptor, this article from Angular In Depth will help you understand how you can apply it to your application, and you should. To summarize, you can use interceptors to, amongst other things:

  • Replace/ urls on the fly
  • Hide/Show a loader when loading things
  • Add request headers (CORS)
  • Handle errors
  • Log http operations
  • Create a fake backend
  • Authentication

As you can see, it’s a pretty nifty way to handle some classic requirements of most modern apps.

Setting up an HttpInterceptor

Creating an HttpInterceptor is very straight forward. In general, you want to create you interceptor as a singleton, so it can be used across your application and only instantiated once by Angular’s DI. This involves for your average Angular app:

  • Adding the provider definition to the providers configuration of CoreModule
  • Importing HttpClientModuleinto the imports configuration of CoreModule
  • Making sure that no other module imports HttpClientModule in your application once CoreModule is imported in your AppModule. If you don’t use CoreModule (Best practice warning, you should), you can directly import HttpClientModule and your interceptor provider definition into AppModule.

One of the key requirements here is that HttpClientModule is only imported once in your application. The reason why you would want this module to be only be imported once is that this is the module from which we import theHttpClientservice, which is used throughout the application to make HTTP calls. This service is set to handle interceptors should they exist but all interceptors are set up to wok on that single instance of the HttpClient service. Understanding this plays an important role in the next section.

Lazy Loaded Modules and HttpInterceptor

Lazy loaded modules is a performance related Angular feature that allows modules to only be loaded when their route is visited. This allows your application to bootstrap a lot faster by initially only downloading just the necessary code to get the user to the first actionable screen of your application. One characteristic of lazy loaded modules is that any service declared in the providers configuration is only available to that module, in Angular speak: “providers of lazy-loaded modules are module-scoped”.
Even more specifically:

When the router creates a component within the lazy-loaded context, Angular
prefers service instances created from these providers to the service instances of the application root injector.

It doesn’t work!

In my case, I set up a token interceptor that was supposed to be shared
application wide and adds an authentication token to all API bound requests. Debugging showed my that for some calls, the interceptor was getting used, but any other HTTP request from within any of my feature modules was not going through the interceptor at all. Through some more debugging and research, which included some interesting side clarifications on CORS preflight request specification (Did you know that one of a condition for a request to be preflighted is if it sets custom headers in the request (e.g. the request uses a custom header such as x-auth-token?), I came to better understand why my interceptor was not getting hit.

The Why

It came down to having one of the other NPM packages I use in my lazy loaded modules ALSO importing the HttpClientModule . Everytime that happens, a new instance of the HttpClient service is injected in the module that of course has not been configured to use the interceptor configured in the CoreModule .
I could not find a good workaround for this situation short of redeclaring the interceptor provider configuration on each lazy loaded module as a short term workaround while I figure out which package is importing the HttpClientModule or if Angular accounted for this use case and allows modules to be loaded only if they have not been previously been.

The Fix

As of now, I don’t have a clear way to fix this short of not using the libraries that import HttpClientModule or adding the module-scoped definition of the interceptor provider to each lazy loaded feature.

I’d appreciate if you ran into this issue or similar and found a way to solve it
in an elegant way to share it in the comments.

Discussion (10)

Collapse
karunalakshman profile image
KarunaLakshman

Hi Abou,

I believe if you use @SkipSelf() decorator for HttpClient service in lazy module, DI mechanism will inject the HttpClient service instance from parent injector. So your interceptor in parent injector might interceptor the service calls of lazy module. please give a try. I very often use @SkipSelf to access service instance from parent injector but haven’t tried especially for HttpClient service. This might help you. Good luck!!

Collapse
dudumanbogdan profile image
Duduman Bogdan Vlad

Shouldn't HttpClient send same instance from CoreModule? Meaning, why is needed the SkipSelf() in lazyModule

Collapse
javarome profile image
Jérôme Beau

Thanks for this analysis Abou, this helped.

I had that problem too, but not caused by third-party modules. So I could solve by making sure that HttpClientModule is imported only once, in the AppModule (above/before the the lazy-loaded ones).

Collapse
jagpreetsingh83 profile image
jagpreetsingh83

I imported HttpClientModule in AppModule instead of the SharedModule and it worked for me.

Collapse
gidkom profile image
Gideon Kombian

this worked. thanks.

Collapse
_b0b1_ profile image
БоБи

Yes, that also worked for me.

Collapse
janatbek profile image
Janatbek Sharsheyev

I had the same problem, in my case I was using HttpHandler and not HttpClient to avoid circular dependency. I noticed that I changed in all places, in the service in another module as well, which I shouldn't touch. so I put httpClient back and it worked.

Collapse
villanuevand profile image
Andrés Villanueva • Edited on

Hi Abou,

Great explanation about the problem. This post and the comments bellow help me a lot to solve the problem.

Also I learned about the @Optional and @SkipSelf Decorators
In my cases I got interceptors in CoreModule.

I just moved the HttpClientModule to the CoreModule and removed form other Lazy loaded modules and it works pretty well.

Collapse
bgdnbc91 profile image
Bogdan

Hi Abou,

Nice post. I have the same issue with my interceptor. Did you find a solution for this issue after all?

Regards!

Collapse
villanuevand profile image
Andrés Villanueva

Hey @bogdan

You should move the HTTPClientModule to your CoreModule.