Skip to content

RFC: Introduce new "effect" function as an alternative/replacement for the @Effect() decorator #1368

@MikeRyanDev

Description

@MikeRyanDev

The @Effect() decorator is used to annotate classes with metadata about which properties on the class are effects. At runtime @ngrx/effects reads this metadata to determine which properties on the class instance should be subscribed to. This has four disadvantages:

1. No Type Checking

This would be legal using the decorator and would result in a runtime error:

class MyEffects {
  @Effect() source$ = 'not an observable';
}

Similarly, it can't type check that the items emitted from an observable are actions if dispatch is true.

This would be legal using the decorator and would also result in a runtime error:

class MyEffects {
  @Effect() source$ = observableOf('not an action');
}

2. Incompatibility with Closure

More information about this is captured in #362, but basically the @Effect() decorator does not work out of the box with Closure's property renaming. It requires that the developers write an additional interface to declare the property names so that Closure doesn't rename them.

3. Requires a reflect-metadata polyfill

All built-in Angular decorators are removed when compiled with the AOT compiler. We can't tap into this, so the @Effect() decorator needs the reflect-metadata polyfill even for production builds.

4. Needs defer for testing some effects

If an effect uses some other observable to start you typically need to wrap it in defer(...) so that you have an opportunity to interact with the source service in a test:

class MyEffects {
  @Effect() source$ = defer(() => {
    return mySocketService.connect();
  });
}

Alternative

I recommend adding a new effect function that can be used instead of the @Effect() decorator to address the above concerns:

class MyEffect {
  source$ = effect(() => this.actions.ofType(...));

  source$ = effect(() => {...}, { dispatch: false });
}

At runtime @ngrx/effects would enumerate over the properties of each registered class instance and look for any effects that were created with the effect(...) function.

This function can be strongly typed to verify that an observable is always returned by the inner callback and that the returned stream is a stream of Actions if dispatch is true. Here is the proposed type signature:

export function effect<T>(source: () => Observable<T>, options: { dispatch: false }): Observable<T>;
export function effect<T extends Action>(
  source: () => Observable<T>,
  options?: { dipsatch: true },
): Observable<T>;

If accepted, I would be willing to submit a PR for this feature

  • Yes (Assistance is provided if you need help submitting a pull request)
  • No

cc @alex-okrushko @rkirov

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions