23

I am updating @ngrx/effects from 1.x to 2.x

In 1.x I have access of state tree in effect:

  constructor(private updates$: StateUpdates<AppState>) {}

  @Effect() bar$ = this.updates$
    .whenAction(Actions.FOO)
    .map(obj => obj.state.user.isCool)
    .distinctUntilChanged()
    .filter(x => x)
    .map(() => ({ type: Actions.BAR }));

Now in 2.x, it only gives me action. Is there still a way to get access of the state tree? Or should I avoid using like this because it is not a good practice?

  constructor(private actions$: Actions) {}

  @Effect() bar$ = this.actions$
    .ofType(ActionTypes.FOO)
    .map((obj: any) => {
      console.log(obj);              // here is action only
      return obj.state.user.isCool   // so it is wrong here
    })
    .distinctUntilChanged()
    .filter(x => x)
    .map(() => ({ type: ActionTypes.BAR }));

2 Answers 2

26

Another way is using .withLatestFrom(this.store). So the complete code is:

  constructor(
    private actions$: Actions,
    private store: Store<AppState>
  ) {}

 @Effect() bar$ = this.actions$
    .ofType(ActionTypes.FOO)
    .withLatestFrom(this.store, (action, state) => state.user.isCool)
    .distinctUntilChanged()
    .filter(x => x)
    .map(() => ({ type: ActionTypes.BAR }));
Sign up to request clarification or add additional context in comments.

3 Comments

Also, if you use this.store.select(fromRoot.getUserIsCool) you don't have to use distinctUntilChanged()... right?
Passing the store to withLatestFrom throws TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.. It does work though. Any idea why I get the error?
It works for me and saving my day.Thanks! Could you explain a bit about the action where the "withlastestFrom" receive? it just shows type "never" in my code
5

Effects don't have to be class properties; they can be methods, too. Which means you can access a store that's injected into the constructor.

At the time of writing this answer, the semantics of property declarations and public/private constructor parameters were not clear to me. If properties are declared after the constructor, they can access the public/private members declared via constructor parameters - so you don't have to declare your effects as functions.

With the injected store, you should be able to use an operator like mergeMap to get the state and combine it with the update you received:

@Effect()
bar$(): Observable<Action> {

  return this.actions$
    .ofType(ActionTypes.FOO)
    .mergeMap((update) => this.store.first().map((state) => ({ state, update })))
    .map((both) => {
      // Do whatever it is you need to do with both.update and both.state.
      return both.update;
    })
    .distinctUntilChanged()
    .filter(x => x)
    .map(() => ({ type: ActionTypes.BAR }));
  }
}

Whether or not doing this is good practice is a matter of opinion, I guess. Reading the state - ideally by composing an ngrx-style selector - sounds reasonable, but it would be cleaner if all of the information you needed for a particular effect was included in the action to which it was listening.

3 Comments

I've edited my answer. I tested what I'd written - off the top of my head - and my app went berserk, as combineLatest resulted in cycle of state updates. Added a comment re: good practice, too.
thank you so much! Your first try is on the right direction, need change to withLatestFrom
Yep, withLatestFrom is a much better solution.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.