3
votes

I have two applications in a mono-repo, one will be a free subset of the other, so all of the functionality in the free one is included in the not-free one.

I'm trying to set up my Effects so that there is a core effect class that implements all of the common effects and then extend that in the not-free project to add additional effects. This isn't working. Both apps can use the core effects as long as that's all the effects I define, but as soon as I add more effects to the class for the not-free, it stops recognizing the core effects.

Here's the set up:

  • core.effects.ts includes the CoreEffects class which defines 2 vanilla effects - LoadLayout and ToggleSidebar
  • not-free.effects.ts includes NotFreeEffects class which extends CoreEffects class
  • free.effects.ts includes FreeEffects class which extends CoreEffects class and does not define any additional effects.
  • In my module, I import Effects module and specify the inherited effects class: EffectsModule.forRoot([NotFreeEffects]) and EffectsModule.forRoot([FreeEffects]). If I'm understanding this correctly, this means that my inherited classes are set up correctly as the Core Effects are still seen (unless I define other Effects in the inherited class)

As an example, the LoadLayout effect looks like this (the others are similar):

@Effect()
  loadLayout$: Observable<Action> = this.actions$
    .ofType(CoreLayoutActions.LOAD_LAYOUT)
    .switchMap(() => this.layoutService.loadLayout())
    .map(
      (layoutState: ICoreLayoutState) =>
        new CoreLayoutActions.LoadLayoutSuccessAction(layoutState)
    );

When I run either app with this set up, everything works. I added log statements in my reducers so I could see when the LoadLayoutSuccess message is passed and handled in the reducer.

As soon as I add another effect to the NotFreeEffect class (i.e. one not used by the free app), the core Effects are no longer handled - my log statements in the reducers no longer indicate that LoadLayoutSuccess is ever dispatched. Nothing has changed except adding a totally unrelated Effect to the not-free class. The free app continues to work, the not-free does not. If I remove that additional Effect from not-free, it begins recognizing the Core Effects again.

What am I missing? Should you be able to inherit effect classes like this?

Details:

  • Angular CLI: 1.7.1
  • Node: 8.9.4
  • OS: win32 x64
  • Angular: 5.2.7 ... animations, common, compiler, compiler-cli, core, forms ... language-service, platform-browser, platform-browser-dynamic ... router

  • @angular/cdk: 5.2.5

  • @angular/cli: 1.7.1
  • @angular/material: 5.2.5
  • @angular-devkit/build-optimizer: 0.3.2
  • @angular-devkit/core: 0.3.2
  • @angular-devkit/schematics: 0.3.2
  • @ngtools/json-schema: 1.2.0
  • @ngtools/webpack: 1.10.1
  • @schematics/angular: 0.3.2
  • @schematics/package-update: 0.3.2
  • typescript: 2.6.2
  • webpack: 3.11.0
  • ngrx: 5.2.0
  • @nrwl/nx: 1.0.3

Thanks,

TTE

3

3 Answers

5
votes

Ngrx < 8:

This is a known issue with various symptons. Essentially, using extends in the context of effects is buggy. My experience is that the effects in the baseclass work as long as you don't have any effects in the subclass.
https://github.com/ngrx/platform/issues/586
https://github.com/ngrx/platform/issues/1234

UPDATE: Since this commit https://github.com/ngrx/platform/commit/e7ae8a228ee4338b6beac4baf0bba745fa32c917#diff-708a6e5fc1424e81075508aee61cb59f effects no longer work at all when subclassed. Reason "hasOwnProperty"-usage instead of direct access of the effects-metadata-property.

Unfortunately the Ngrx team does not want to change this behavior
https://github.com/ngrx/platform/pull/890

For a workaround, see this answer.

Ngx >= 8

This is now possible by using the createEffect-function (they have implemented it differently it seems). Also see this answer

3
votes

You can try this way.

abstract class GenericEffects {

 protected constructor(
   protected actions$: Actions,
 ) {}

 protected _someGenericEffect$ = this.actions
  .pipe(
      ofType('GENERIC_ACTION')
      ...
    )
}


class SpecificEffects extends GenericEffects {
    @Effect()
    someSpecificEffect$ = this._someGenericEffect$;
}

2
votes

NgRx provides a function createEffect since version 8.0.0-beta.0.

For the NgRx effects to work in inherited classes simply replace your decorated effects with the new createEffect functions like in the snippet below:

import { createEffect, ofType } from '@ngrx/effects';

loadLayout$ = createEffect(() => this.actions$...);

You can install the latest beta by running(you might also need to update rxjs to @latest):

npm install --save @ngrx/store@next @ngrx/effects@next @ngrx/schematics@next @ngrx/store-devtools@next