NgRx how to dispatch 2 actions in order
Asked Answered
A

2

6

I cant seem to find a way with the NgRx (not RxJS Style) to dispatch 2 Actions in an effect.

I would like to (IN THIS ORDER):

  1. delete a Movie in the Database with an effect,
  2. dispatch deleteMovieSuccess
  3. dispatch getMovies (I need to reload all Movies afterwards!)

I tried to do it like below, but it just fires the first Action, and I cannot see the other action: In my log I can see:

  • [Movie list] Delete movie
  • [Movie list] Get movies
  • [Movie list] Get movies successful

I have the folloing actions:

export const getMovies = createAction('[Movie List] Get Movies', props<{search: string, page: number, limit: number}>());
export const getMoviesSuccess = createAction('[Movies List] Get Movies Success', props<{ search: string, page: number, limit: number }>());

export const deleteMovie = createAction('[Movie List] Remove Movie', props<{ movieToDelete: Movie, search: string, page: number, limit: number }>());
export const deleteMovieSuccess = createAction('[Movie List] Remove Movie Success', props<{ movieToDelete: Movie }>());

and the following effect:

    deleteMovie$ = createEffect(() => this.actions$.pipe(
      ofType(MovieActions.deleteMovie),
      mergeMap(({movieToDelete, search, page, limit}) =>
        this.moviesService.deleteMovie(movieToDelete)
        .pipe(
          map(() => MovieActions.deleteMovieSuccess({movieToDelete: movieToDelete})),
          map(() => MovieActions.getMovies({search, page, limit})),
          catchError(() => EMPTY)
        )
      )
    )
  );

How can I trigger BOTH, deleteMoviesSuccess, and getMovies in this order?

Ive also tried with switchMap and and flatMap, but never are both Actions dispatched correctly. I just cant seem to understand, how dispatching in an iterative way is possible, but I really need it for my special usecase.

Any help is greatly appreciated.

Addax answered 18/11, 2021 at 21:22 Comment(0)
M
4

You should not dispatch two action in as a result of an effect. Rather dispatch some kind of success action and then react on that in other effects. Read here

You could create a chain of effect:

  1. dispatch delete action
  2. on finish of delete dispatch deleteSuccess action
  3. on deleteSuccess action trigger loadMovies effect
  4. on loadMovies success some set action for a reducer to pick up
deleteMovie$ = createEffect(() => this.actions$.pipe(
    ofType(MovieActions.deleteMovie),
    mergeMap(({movieToDelete, search, page, limit}) => this.moviesService.deleteMovie(movieToDelete).pipe(
        map(() => MovieActions.deleteMovieSuccess({movieToDelete: movieToDelete})),
        catchError(() => EMPTY)
    ))
));

loadMovie$ = createEffect(() => this.actions$.pipe(
    ofType(MovieActions.deleteMovieSuccess),
    mergeMap(() => this.moviesService.loadMovies().pipe(
         map(movies => MovieActions.setMovies({ movies })),
         catchError(() => EMPTY)
    ))
));

EDIT
Also instead of passing parameters like limit or search you may hold these in the store. Doing so gives you the advantage to always access those in effects when needed. The NgRx Documentation has a great example on how this selecting in an effect is done. ngrx.io/api/effects/concatLatestFrom

Millibar answered 18/11, 2021 at 22:1 Comment(4)
I like this approach, as it is simple to understand and follow, but how would I pass the arguments from the first effect to the second one? I have to pass {search, page, limit} to the second effect, as I have to call getMovies() with those exact parameters. Should I just pass them into the deleteMovieSuccess action? will they be accessible in the second effect?Addax
Passing it in the deleteMovieSuccess is definitely possible but i'd rather have these parameters in the store and instead of ever sending them with an effect, you just select the from the store. ngrx.io/api/effects/concatLatestFromMillibar
Could you incorporate this in your answer for future readers to find? Then I think this is the accepted way to go...Thank you!Addax
Done! Also look into that NgRx linter. I think it really help to keep the code cleanMillibar
S
1

you could use the switchMap operator to return an array of actions.

For example, instead of;

    deleteMovie$ = createEffect(() => this.actions$.pipe(
      ofType(MovieActions.deleteMovie),
      mergeMap(({movieToDelete, search, page, limit}) =>
        this.moviesService.deleteMovie(movieToDelete)
        .pipe(
          map(() => MovieActions.deleteMovieSuccess({movieToDelete: movieToDelete})),
          map(() => MovieActions.getMovies({search, page, limit})),
          catchError(() => EMPTY)
        )
      )
    )
  );

You could try;

    deleteMovie$ = createEffect(() => this.actions$.pipe(
      ofType(MovieActions.deleteMovie),
      mergeMap(({movieToDelete, search, page, limit}) =>
        this.moviesService.deleteMovie(movieToDelete)
        .pipe(
          switchMap(() => [MovieActions.deleteMovieSuccess({movieToDelete: movieToDelete}), 
MovieActions.getMovies({search, page, limit})]),
          catchError(() => EMPTY)
        )
      )
    )
  );
Siena answered 18/11, 2021 at 21:46 Comment(3)
Will this follow the exact order in which the Actions are passed? If so that is a nice solution I thinkAddax
You should not nested switch/merge maps. Rather look into chaining effect. This way the whole code becomes more read and maintainable.Millibar
@Addax Since this is an array, any passed actions in the array would be launch in the same order of the array index.Siena

© 2022 - 2025 — McMap. All rights reserved.