I need to have some Angular services configured dynamically, depending on a runtime switch. In days before AOT, I got it to work using the following code:
@NgModule({
imports: [HttpModule],
providers: []
})
export class MyModule {
static forRoot(config: MyConfiguration): ModuleWithProviders {
return {
ngModule: MyModule,
providers: [
SomeService,
{
provide: SomeOtherService,
useFactory: (some: SomeService, http: Http) => {
switch (config.type) {
case 'cloud':
return new SomeOtherService(new SomethingSpecificForCloud());
case 'server':
return new SomeOtherService(new SomethingSpecificForServer());
}
},
deps: [SomeService, Http]
},
]
};
}
}
Then in my AppModule
I would import this as MyModule.forRoot(myConfig)
.
As I updated CLI and Angular, this no longer compiles, because it cannot be statically analyzed. I understand why, but I am still not sure what the correct way to solve it is.
Have I abused this forRoot()
approach in the first place? How do you write modules so that depending on a runtime switch, they produce different services?
I found one way to achieve it: Expose the configuration via a provider, then have injected to a "static" factory function. The code above would look like this:
// Necessary if MyConfiguration is an interface
export const MY_CONFIG = new OpaqueToken('my.config');
// Static factory function
export function someOtherServiceFactory(config: MyConfiguration,some: SomeService, http: Http) {
switch (config.type) {
case 'cloud':
return new SomeOtherService(new SomethingSpecificForCloud());
case 'server':
return new SomeOtherService(new SomethingSpecificForServer());
}
}
@NgModule({
imports: [HttpModule],
providers: []
})
export class MyModule {
static forRoot(config: MyConfiguration): ModuleWithProviders {
return {
ngModule: MyModule,
providers: [
SomeService,
{ provide: MY_CONFIG, useValue: config },
{
provide: SomeOtherService,
useFactory: someOtherServiceFactory,
deps: [MY_CONFIG, SomeService, Http]
},
]
};
}
}
It works and all, but I would still be very interested in knowing whether this is actually a good idea, or if I'm doing something terribly wrong and should be taking a completely different approach to solving this problem.
I found another solution:
- Use Angular CLI environments.
- Create an abstract classes/interfaces for the services with different implementations or dependencies for different environments.
- Export the right type from each enviromnent file (who said it has to only be a plain JS object?).
- In the module provider definition, import from the environment.
- At compile time, CLI environments will make the right thing get linked.
More information and sample project at my blog.