I encountered a similar issue and managed to resolve it by implementing an interface and creating a list of InjectionTokens. While it may seem like a bit much, this approach provides a great deal of flexibility and can be applied to various other challenges.
For one project, we established a global print service within a shared module, with multiple custom implementations being supplied by individual components. The beauty of this setup is that it eliminates the need to explicitly inject or reference all possible implementations in the global service.
Interface and initial implementation
export interface IPrint {
Print(textInput: string): void;
}
export class FilePrint implements IPrint {
Print(textInput: string): void {
console.log("File Print");
}
}
Creating a list of service implementations using InjectionToken
// Maintaining a list of tokens where providers will supply implementations
export const PrintServiceTokens: Map<string, InjectionToken<IPrint>> = new Map();
// Adding token for File service implementation
PrintServiceTokens.set('file', new InjectionToken<IPrint>('file'));
Provider for File service implementation
providers: [
...
// Providing the first implementation service
{
provide: PrintServiceTokens.get('file'),
useClass: FilePrint
}
]
Adding another implementation (which could be in a separate module)
export class ScreenPrint implements IPrint {
Print(textInput: string): void {
console.log("ScreenPrint");
}
}
Adding a token for the additional implementation
PrintServiceTokens.set('screen', new InjectionToken<IPrint>('screen'));
Setting up the provider
providers: [
// Other implementation service
{
provide: PrintServiceTokens.get('screen'),
useClass: ScreenPrint
}
]
Finally, in a component or service
myPrintService: IPrint;
constructor(private injector: Injector) {
// You can choose which service to use; a simple string could be provided via @Input
this.myPrintService = this.injector.get(PrintServiceTokens.get('file'));
this.myPrintService = this.injector.get(PrintServiceTokens.get('screen'));
}