I have 2 interfaces:
export interface IUserService {
...
}
export interface IUserStorageService {
...
}
And a single service implementing both of them:
@Injectable()
export class UserService implements IUserService, IUserStorageService {
...
}
(This part seems to be irrelevant for the question, so it's just for the sake of completeness. I can easily convert interfaces to abstract classes to use them directly as tokens without additional injection tokens.)
Now, since Angular doesn't support interfaces as tokens for providers, I have to create injection tokens:
export let USER_SERVICE: InjectionToken<IUserService> = new InjectionToken<IUserService>("user.service");
export let USER_STORAGE_SERVICE: InjectionToken<IUserStorageService> = new InjectionToken<IUserStorageService>("user-storage.service");
And now I'm able to map those injection tokens to the single service class globally in my app.module.ts
:
@NgModule({
...
providers: [
{ provide: USER_SERVICE, useClass: UserService },
{ provide: USER_STORAGE_SERVICE, useClass: UserService }
],
...
})
export class AppModule {
...
}
And finally, I'm now able to inject the service under different interfaces to my components:
// Some component - sees service's API as IUserService
constructor(@Inject(USER_SERVICE) private readonly userService: IUserService) {
}
// Another component- sees service's API as IUserStorageService
constructor(@Inject(USER_STORAGE_SERVICE) private readonly userStorageService: IUserStorageService) {
}
The issue here is that Angular actually creates 2 instances of UserService
, one for the each token, while I need UserService
to be a single instance per app.
How can I achieve that?
The Dependency Injection system in Angular uses tokens to uniquely identify a Provider. There are three types of tokens that you can create in Angular. They are Type Token, String Token, and Injection Token. DI Tokens.
The provider-definition key can be one of the following: useClass - this option tells Angular DI to instantiate a provided class when a dependency is injected. useExisting - allows you to alias a token and reference any existing one. useFactory - allows you to define a function that constructs a dependency.
Class Provider: useClassThe useClass expects us to provide a type. The Injector creates a new instance from the type and injects it. It is similar to calling the new operator and returning instance. If the type requires any constructor parameters, the injector will resolve that also.
The useExisting provider key lets you map one token to another. In effect, the first token is an alias for the service associated with the second token, creating two ways to access the same service object.
I had a very similar requirement: I was providing a service to a number of components - the service has a dispatchEvent
function and a subscribeToEvents
function. I want it to be clear that only the managing component can use dispatchEvent()
, so the service implements
two abstract classes (these can then be used as tokens - which is clearer than the injection token format) - one has dispatchEvents, the other has subscribeToEvents
providers: [
{provide: AbstractSettingsManager, useClass: SettingsEventService},
{provide: AbstractSettingsConsumer, useExisting: AbstractSettingsManager}
],
You can use the useExisting
key, and keep in mind that it means "use the existing token"
The components will all have the same ServiceEventService
instance, but as I'm providing the AbstractSettingsManager
/AbstractSettingsConsumer
the components will only have access (via TypeScript checks) to one or other of the services functions
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With