I am trying to understand the use of provider factory in Angular. I understand the examples provided in Angular Documentation.
However, I have encountered a rather unusual usage of provider factory in ngrx EffectsModule (effects_module.ts).
@NgModule({})
export class EffectsModule {
static forFeature(featureEffects: Type<any>[]): ModuleWithProviders {
return {
ngModule: EffectsFeatureModule,
providers: [
featureEffects,
{
provide: FEATURE_EFFECTS,
multi: true,
deps: featureEffects,
useFactory: createSourceInstances,
},
],
};
}
// *** <snip> forRoot method for brevity
}
export function createSourceInstances(...instances: any[]) {
return instances;
}
I understand the purpose of multi:true
.
However, I am having hard time to figure why would featureEffects
be declared as providers, and as well be used as dependency for FEATURE_EFFECTS
- and what does createSourceInstances(...instances: any[])
achieve?
What is this pattern and where can one use it?
Let's start with @ngrx/effects
documentation.
The first thing we need to do is to create an AuthEffects
service
@Injectable()
export class AuthEffects {
@Effect()
...
constructor(private http: HttpClient, private actions$: Actions) {}
}
How do we register effects?
It's easy:
EffectsModule.forRoot([AuthEffects])
or
EffectsModule.forFeature([AuthEffects])
Now, let's stop here and think what is the AuthEffects
.
AuthEffects
is just ordinary angular service that can have any dependencies(HttpClient
and Actions
in this case) that will be resolved with the help of Angular DI system.
And this service we are passing to the EffectsModule
and also we can create more effects:
EffectsModule.forFeature([AuthEffects, MySecondEffects, ...])
Now, let's imagine that we're authors of EffectsModule
.
We gave users the opportunity to provide any count of effects just by creating angular service. We're going to use these services in our library:
addEffects(effectSourceInstance: any) {
this.sources.addEffects(effectSourceInstance);
}
And as you can see we need to have instances of our provided services not only classes.
How can we create these instances?
Maybe like this?
EffectsModule.forFeature([new AuthEffects(new HttpClient(...), new Actions(...))])
Of course not! We can let angular DI to do that:
EffectsModule.forFeature([AuthEffects])
...
forFeature(featureEffects: Type<any>[]) {
....
providers: [
featureEffects,
Now Angular DI knows all these services and how to create them. But i as author also want to use them in my services but i don't know how to get them...
@NgModule({})
export class EffectsFeatureModule {
constructor(
// i need to get all effects provided by users but they are hidden in DI system
...
) {
}
}
Fortunately, we can provide a token that will be used internally and will be known by the author of library.
For that we're creating InjectionToken:
export const FEATURE_EFFECTS = new InjectionToken<any[][]>(
'ngrx/effects: Feature Effects'
);
and defining recipe for FEATURE_EFFECTS
token:
featureEffects,
{
provide: FEATURE_EFFECTS,
multi: true,
deps: featureEffects,
useFactory: (...instances: any[]) => {
return instances;
},
},
multi
tells us that this token can be defined several times (i.e. we have several forFeatures
calls)
deps
specifies that FEATURE_EFFECTS
token will use all featureEffects
instances that will be created by Angular DI.
useFactory
takes these instances as parameter.
This way we know exactly that injection FEATURE_EFFECTS
token gives us all instances of effects.
Let's think if we could omit featureEffects
, for example:
{
provide: FEATURE_EFFECTS,
multi: true,
useValue: featureEffects
},
If we do so then we won't get instances but rather array of classes(functions). But we need to have all instantiated classes and only Angular DI is the best friend here.
And finally, although Angular DI is quite powerful pattern, it also has some restrictions.
In AOT the following code:
featureEffects,
{
provide: FEATURE_EFFECTS,
multi: true,
deps: featureEffects,
useFactory: (...instances: any[]) => {
return instances;
},
},
will result in the error:
Function expressions are not supported in decorators in 'EffectsModule' Consider changing the function expression into an exported function.
So that's why it's written as follows:
providers: [
featureEffects,
{
provide: FEATURE_EFFECTS,
multi: true,
deps: featureEffects,
useFactory: createSourceInstances,
},
],
...
export function createSourceInstances(...instances: any[]) {
return instances;
}
And we can successfully get these instances inside the library:
@NgModule({})
export class EffectsFeatureModule {
constructor(
...
@Inject(FEATURE_EFFECTS) effectSourceGroups: any[][],
...
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