Angular 6 providedIn - how to customize the @Injectable() provider for dependency injection?
Asked Answered
P

1

13

In Angular 5, if I had AbstractClassService and ExtendedClassService that extends the abstract, I could do this in my NgModule's providers array:

@NgModule({
  providers: [
    {provide: AbstractClassService, useClass: ExtendedClassService}
  ]
})
export class AppModule {}

This would allow me to switch ExtendedClassService with another for testing or whatever very easily. This can still be done with Angular 6, however there is the new providedIn option that can be set within the service itself to reduce bundle size:

@Injectable({providedIn: 'root'})
export class ExtendedClassService extends AbstractClassService {}

Is there a way for me to accomplish the same thing I had with Angular 5 while using the new providedIn? Something like this:

@Injectable({providedIn: 'root', provide: AbstractClassService})
export class ExtendedClassService extends AbstractClassService {}
Padlock answered 23/5, 2018 at 13:14 Comment(4)
providedIn is just for locating the injector which will be responsible for injecting the instance of the class ExtendedClassService , For this it will be root injector. If you don't want root injector for this you can specify any other module .Then the service instance will be created by that injector which includes that module.Ajit
you can use useFactory to provide a factory function that is responsible for creating the instance if you want to create it dynamically.Ajit
I don't think so it is possible to provide alternate implementation for existing providers in newer TreeShakable provider creation syntax (provideIn)... Please check this blog it has covered most of it softwarearchitekt.at/post/2018/05/06/…Romero
@Ajit I do want the root injector to be responsible for injecting the service, but I want it to use the token AbstractClassService -- because that's is what is injected in the components -- not ExtendedClassServicePadlock
P
14

I needed to do two things.

First, use implements instead of extends when creating the inheriting class and do not use the providedIn key there:

@Injectable() // removed providedIn
export class ExtendedClassService implements AbstractClassService {}

Second, add the provider instructions to the abstract class instead:

@Injectable({providedIn: 'root', useClass: ExtendedClassService})
export abstract class AbstractClassService {}

Other provider configuration (useValue, useExisting, useFactory) can also be used there.

Credit goes to Abinesh with this comment which led me to the linked blog post. Many thanks to the blog author!

Padlock answered 23/5, 2018 at 18:43 Comment(7)
If ExtendedClassService has any dependencies e.g. HttpClient, they have to be added to the AbstractClassService decorator: deps: [HttpClient]Gametophyte
this produces a circular reference if u place services in separate files. AnnoyingAccessible
How to go if the base class is not ours, like say the ErrorHandler class of Angular core for example ?Remy
@EvAlex. you can avoid circular references (dependencies) if you literally only export/import Class names in the separate files. If you try and share variables or enums you will get the circulars. I ran into this and solved it by moving any shared variables to a separate file. (In my case it was simply an Enum that I moved into its own file).Daytime
You can even omit the @Injectable() in the ExtendedClassService entirely.Dissimulation
@Dissimulation actually no, in this mode it won't work. You will get an error: "ɵfac is not a function". In this mode a factory for a class required and this factory is being created for classes decorated with @Injectable(). Agree, it is strange but it is as it isVelvety
@DmytroMezhenskyi thanks for the hint, maybe you need it since a specific Angular version or since ivy. I saw about this change angular.io/guide/migration-undecorated-classes , but they only mention directives and components and not injectables.Dissimulation

© 2022 - 2024 — McMap. All rights reserved.