NgRx how to dispatch 2 actions in order

Viewed 399

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?

I`ve also tried with switchMap and and faltMap, 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.

2 Answers

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

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)
        )
      )
    )
  );
Related