Alexander Abakumov
Alexander Abakumov

Reputation: 14579

Provide the same service instance under different tokens

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?

Upvotes: 14

Views: 2848

Answers (1)

Drenai
Drenai

Reputation: 12407

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

Upvotes: 16

Related Questions