How to dispatch multiple actions in ngrx/effect (redux-observable)?

21,388

Solution 1

An effect transforms a stream of actions, so you have a stream of actions as input and output. In your example, you map an action to an array of actions. A stream of arrays of actions is not a valid output type. You need to flatten that array, meaning that you do not emit the array itself into the output stream but instead each of its elements.

Instead of:

input:  --a-------a------>
output: --[b,c]---[b,c]-->

You should do:

input:  --a-------a------>
output: --b-c-----b-c-->

For flattening an Observable of array into Observables of each element, you can use one of the operators mergeMap, switchMap, exhaustMap. In most cases, mergeMap will be the right choice. If you want to learn more about these operators, have a look at this answer.

@Effect()
register$: Observable<Action> = this.actions$.pipe(
  ofType(AuthActionTypes.REGISTER_REQUEST),
  mergeMap((action: RegisterRequest) => {
    // check for register request success
    return [
      new RegisterSuccess(),
      new LoginRequest(action.payload)
    ]
  })
);

Solution 2

I have had same situation (and assuming NgRx 10 or higher), I have a different perspective, more fundamental way how to use effects. Triggering sequentially multiple actions in one place, specially within a single effect, is anti-pattern. In essense, its important to keep a consistent general flow of application state in NgRx of actions and potentials reductions. Just as the NgRx architecture foresees it.

Following the 3 effect rules will help already to avoid difficult situations:

  1. Name effects to be the name of the effect
  2. Make your effect only do one thing
  3. Emit only one action

That way, it helps you to follow the separation of concerns design pattern which of course also help you the NgRx effects to become a lot more unit testable.

Back to your example, you can simple decouple what you wanted to do (2 additional actions) with an in-between-proxy action.

In your case it seems that you may not need even your original effect dispathMultipleActions$, unless special logic within appears. (which perhaps, may belong into a state Reducer, which is even more unit testable).

Assuming that the ActionTypes.UpdateSomething has a array payload object already, you could split your dispathMultipleActions$ into single ones, so you could do something like this:

@Effect()
deleteAction$ = this.actions$.pipe(
    ofType(ActionTypes.UpdateSomething),
    concatMap(from(new Promise((array) => {
        array.forEach(item => {
            if (item > 3) {
                //do something
            }
        });
    }))),
    {dispatch: false}
);

@Effect()
changeAction$ = this.actions$.pipe(
    ofType(ActionTypes.UpdateSomething),
    concatMap(from(new Promise((array) => {
        array.forEach(item => {
            if (item <= 3) {
                //do something
            }
        });
    }))),
    {dispatch: false}
);
Share:
21,388
mr__brainwash
Author by

mr__brainwash

Updated on September 16, 2021

Comments

  • mr__brainwash
    mr__brainwash over 2 years

    I am using Angular 6, ngrx/store. I have such effect that is responsible for updating things. Depends on some logic I want to dispatch different actions. What is the difference if I use switchMap insted of map?

    This is what I tried but it doesn't work:

     @Effect()
      dispathMultipleActions$ = this.actions$.pipe(
        ofType(ActionTypes.UpdateSomething),
        map(() => {
          const actions: Action[] = [];
          const array = [1, 2, 3, 4, 5];
          array.forEach(item => {
            if (item > 3) {
              actions.push(new DeleteAction(item));
            } else {
              actions.push(new ChangeAction(item));
            }
          });
          return actions;
        })
      );
    
  • Didier68
    Didier68 over 3 years
    Hello, it seems that it no longer works with NGRX 10: as soon as I put an array to return 2 actions (which work alone), I have a TS error saying the type []is incompatible with an 'Action'...
  • Didier68
    Didier68 over 3 years
    To complete my comment: the solution seems to be: ` this.store$.dispatch(RegisterSuccess()); return LoginRequest(action.payload);
  • Anton
    Anton over 3 years
    Can you pls clarify - why array of actions is considered to be an array of observables in this case ? Action != Observable. Though i also use this pattern and it works as expected