I have an Angular project which I compile with AOT. I want to be able to register ClassProvider
that is resolved dynamically according to configuration. Simplified code I use is this:
const isMock = Math.random() > 0.5;
@NgModule({
// ...
providers: [
{ provide: MyServiceBase, useClass: (isMock) ? MyServiceMock : MyService },
],
bootstrap: [AppComponent]
})
export class AppModule { }
The problem is when I compile this with AOT I always get the same service. I would expect to get different service while hitting F5 (because of the randomness
on the first line). When compiling without AOT it behaves as I expect.
Here is the whole code example on github: https://github.com/vdolek/angular-test/tree/aot-conditioned-provider-problem. It behaves differently with ng serve
and ng serve --aot
.
How can I achieve this? I know I could use FactoryProvider
, but then I would have to duplicate the services dependencies (parameters of the factory function and deps property on the FactoryProvider
).
To achieve the dynamic nature of your requirement, you need to use factory providers, via the useFactory
attribute.
I've forked your repository, and amended your app.module.ts as follows, to work in AOT.
Amend app.module.ts
as follows
export let myServiceFactory = () => {
const isMock = Math.random() > 0.5;
return isMock ? new MyServiceMock() : new MyService();
};
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [
{provide: MyServiceBase, useFactory: myServiceFactory},
],
bootstrap: [AppComponent]
})
export class AppModule {
}
In the case that your service is dependent on other services, which, most likely it will, you can use the deps
argument, to pass on the required dependencies.
Let's say that MyServiceBase
is dependent on two services, MyService1
and MyService2
... Your factory function will look as follows :
export let myServiceFactory = (service1:MyService1, service2:MyService2) => {
const isMock = Math.random() > 0.5;
return isMock ? new MyServiceMock(service1, service2) : new MyService(service1, service2);
};
and your providers decleration would look as follows
providers: [
{
provide: MyServiceBase,
useFactory: myServiceFactory,
deps: [MyService1, MyService2]
},
]
This guide contains further detail on the various ways of achieving dependency injection in Angular.
I think as @jeanpaul-a said you don't have any choice other than to use factory. But managing dependencies could be not very clean. But what you could use is the Injector. I'll go with something like:
@NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [ AppComponent, HelloComponent ],
providers: [
Dep1Service,
Dep2Service,
{ provide: MyServiceBase, useFactory: createService, deps: [Injector] }
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
export function createService(injector: Injector) {
const isMock = Math.random() > 0.5;
if (mock) {
return new MyService1(injector.get(Dep2Service));
} else {
return new MyService2(injector.get(Dep1Service));
}
}
What you could also do is to set MyServiceBase as an interface and use InjectionToken. You will find a working example here (not your class name however).
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