Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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;
    })
  );
like image 814
mr__brainwash Avatar asked May 31 '18 16:05

mr__brainwash


People also ask

How do you call an effect in NgRx?

You need to run this command npm install @ngrx/effects — save to install required dependencies. You have to define the actual services to make the API calls. Finally, you need to register all the effects with the EffectsModules and import this module into your main module or feature module.

What is store dispatch NgRx?

They listen for actions dispatched from ngrx/store, meaning that we can dispatch something like a LOAD Action and listen to this action into an effect executing a particular code. They isolate side effects from components, allowing for more pure components that select state and dispatch actions.

What are NgRx actions?

Ngrx actions are simple JSON objects that implement the Action interface provided by @ngrx: export interface Action { type: string; } The type property is a string used to uniquely identify your action to your application.

How does NgRx effect work?

Most effects are straightforward: they receive a triggering action, perform a side effect, and return an Observable stream of another action which indicates the result is ready. NgRx effects will then automatically dispatch that action to trigger the reducers and perform a state change.


2 Answers

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)
    ]
  })
);
like image 187
Kim Kern Avatar answered Oct 16 '22 11:10

Kim Kern


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}
);
like image 43
Igor Lino Avatar answered Oct 16 '22 09:10

Igor Lino