Angular 6: provide HTTP_INTERCEPTORS for 'root'
Asked Answered
S

3

15

With the change from Angular 5 where you provide service in AppModule to Angular 6 where you set 'provideIn' key in @Injectable decorator I have changed all services to use new "provideIn" method. However, exception is my Interceptor Service.

How can I provide HTTP_INTERCEPTORS token for 'root' and use InterceptorService?

this is the Angular 5 way I use atm:

@Injectable()
export class InterceptorService implements HttpInterceptor {
...
}

in AppModule:

providers: [{
  provide: HTTP_INTERCEPTORS,
  useClass: InterceptorService,
  multi: true
}]

But what would be Angular 6 way?

I've tried something like

@Injectable({
  provideIn: 'root',
  useValue: HTTP_INTERCEPTORS,
  deps: [forwardRef(() => InterceptorService)]
})
export class InterceptorService implements HttpInterceptor {
...
}

and a lot of other variants with Injectable but can't seem to figure out how to make it work without writing an object literal directly into providers of a module.

Sorcha answered 7/5, 2018 at 9:38 Comment(1)
The docs still show using "the Angular 5 way": angular.io/guide/http#provide-the-interceptor. See e.g. angular.io/guide/… for when you might use the new version.Mons
W
5

The provideIn-property of Angular 6 is just an addition to the behaviour in Angular 5. If you want to provide something with an already existing InjectionToken, you still have to use the { provide: ClassA, useClass: ClassB } syntax.

See -> https://angular.io/guide/dependency-injection-in-action#external-module-configuration

tl;dr: The way you provide HTTP_INTERCEPTORS has not changed in Angular 6 and there is no "Angular 6"-way.

Woeful answered 7/5, 2018 at 9:55 Comment(6)
That seems rather inconsistent, given that they changed the preferred way of defining how a class is provided.Bess
@MilošTomšik The whole provideIn-thing is (as far as I understand) to make the services, tree-shakeable. So by importing ClassA in a component, Angular will look at ClassA and see that it should be provided in root. But if you have to provide something under something else than its classname, you never reference to this file and angular will have no way of telling, what to provide.Woeful
Additionally it would be really hard for developers to keep track of which class is beeing used in which part of the application.Woeful
This is inaccurate: "If you want to provide something with an InjectionToken other than its class, you still have to use the {provide: ClassA, useClass: ClassB}" See this answer.Repairman
@Repairman i edited the answer to apply on a more general scope. But i honestly cannot imagine a scenario where the method you mentioned is reasonableWoeful
@Woeful I don't understand; what makes it unreasonable?Repairman
C
8

A couple things to note here:

1. providedIn: 'root' is a nice feature but it probably wasn't built for you

As @Leon mentioned, this feature is meant to make services more tree shakeable. It is not meant to completely replace using the providers: [] property of a module. It is an option mostly meant for library developers, not as much for application developers.

Imagine this scenario:

You created a service a few months ago and now your app is no longer using it. You know it's not using it because it's your app and you have full knowledge and control over the codebase. What do you do to that service?

A) Make sure it's using providedIn: 'root' so that Angular can tree shake it out of the bundle since you're not using it anymore

B) Delete the service.

My guess is B!

Imagine another scenario:

You are using a 3rd party Angular module from an npm package. That module has 12 difference services you can use in your app to take advantage of its features. Your app doesn't need all those features so you only inject 3 of those service types into your application components or services.

How do you resolve this?

A) Fork the repository so you can remove all the services your app doesn't use so you don't have to include them in your bundle.

B) Ask the project owner to use providedIn: 'root'. If the library author used providedIn: 'root' the services you don't use don't have an impact on your bundle size and they can stay in the npm package/Angular module for other teams to use if they need them.

My guess is B!

2. providedIn: 'root' doesn't work for interceptors

Interceptors are a multi DI token service which means you can provide multiple values for the same DI token. That token is HTTP_INTERCEPTORS. The @Injectable({...}) decorator exposes no api for providing the decorated type for a different token the way the @NgModule({...}) decorator does.

This means you can't tell Angular Anywhere you would normally ask for 'HTTP_INTERCEPTORS' add this service to the set of values to use instead using the @Injectable({...}) decorator.

You can only do this in a @NgModule({...}) decorator.

3. Providing interceptors is order dependent

Interceptors are a pipeline and the order they are provided in matters in determining the order they get access to the request object (to modify or inspect) and the response object (to modify or inspect).

While some interceptors might be order agnostic you still probably want some determinism in that ordering.

So even if providedIn: 'root' worked for interceptors the order they would be provided in would be determined by the resolution order of types during the Angular compile step - probably not what you want.

Instead providing them in the providers: [] array in an @NgModule({...}) decorator means you can explicitly set the order they will be called in.

Chloro answered 28/8, 2018 at 21:13 Comment(0)
W
5

The provideIn-property of Angular 6 is just an addition to the behaviour in Angular 5. If you want to provide something with an already existing InjectionToken, you still have to use the { provide: ClassA, useClass: ClassB } syntax.

See -> https://angular.io/guide/dependency-injection-in-action#external-module-configuration

tl;dr: The way you provide HTTP_INTERCEPTORS has not changed in Angular 6 and there is no "Angular 6"-way.

Woeful answered 7/5, 2018 at 9:55 Comment(6)
That seems rather inconsistent, given that they changed the preferred way of defining how a class is provided.Bess
@MilošTomšik The whole provideIn-thing is (as far as I understand) to make the services, tree-shakeable. So by importing ClassA in a component, Angular will look at ClassA and see that it should be provided in root. But if you have to provide something under something else than its classname, you never reference to this file and angular will have no way of telling, what to provide.Woeful
Additionally it would be really hard for developers to keep track of which class is beeing used in which part of the application.Woeful
This is inaccurate: "If you want to provide something with an InjectionToken other than its class, you still have to use the {provide: ClassA, useClass: ClassB}" See this answer.Repairman
@Repairman i edited the answer to apply on a more general scope. But i honestly cannot imagine a scenario where the method you mentioned is reasonableWoeful
@Woeful I don't understand; what makes it unreasonable?Repairman
F
3

In Interceptor

@Injectable()
export class InterceptorService implements HttpInterceptor {
...
}

In App Module

providers: [{
  provide: HTTP_INTERCEPTORS,
  useClass: InterceptorService,
  multi: true
}]

"providedIn ... tells Angular that the root injector is responsible for creating an instance of the [service]. Services that are provided this way are > automatically made available to the entire application and don't need to be listed in any module."

"If a provider cannot be configured in the @Injectable decorator of the service, then register application-wide providers in the root AppModule, not in the AppComponent. Generally, register providers in the NgModule rather than > in the root application component."

Furthermore, if the scope of the service should be limited to a feature or branch of the application, provide that service at the top level component for that branch/feature

https://angular.io/guide/dependency-injection-in-action

Ferri answered 18/5, 2018 at 14:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.