Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ngrx EffectsModule - Need assistance in understanding providers usage

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?

like image 336
Wand Maker Avatar asked Jul 08 '18 18:07

Wand Maker


1 Answers

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[][],
...
like image 197
yurzui Avatar answered Sep 24 '22 13:09

yurzui