Search code examples
angulartypescriptngrxngrx-storengrx-effects

Multiple API calls from ngrx effect


I am new with ngrx store and redux pattern. I have a problem when dispatching updatePresentation$ effect. The effect is called when updatePresentation action is called. The updatePresentation action is defined as:

    export const updatePresentation = createAction(
      '[Presentation] Update presentation',
      props<{ presentation: Presentation, files: FileList | null }>()
    );

The updatePresentations$ effect is defined as:

    updatePresentations$ = createEffect(() => {
        return this.actions$
          .pipe(
            ofType(PresentationActions.updatePresentation),
            switchMap(action =>
              this.presentationService.updatePresentation(action.presentation)
                .pipe(
                  tap(presentation => {
                    if (action.files !== null && action.files.length > 0) {
                      this.store.dispatch(PresentationActions.fileUpload({presentationId: presentation.id, files: action.files}));
                    }
                  }),
                  map(presentation => PresentationActions.loadPresentations({pageable: new Pageable()})),
                  catchError(error => of(PresentationActions.updatePresentationFailure({error})))
                )
            )
          );
      });

Basically, what I want to do is update the presentation, when the presentation is updated, I want to check if action.files exists, and if it does dispatch the fileUpload action. After this I want to load the presentations again by returning loadPresentations action (which will call the loadPresentations$ effect). This is how the fileUpload$ and loadPresentations$ effects look like:

      loadPresentations$ = createEffect(() => {
        return this.actions$.pipe(
          ofType(PresentationActions.loadPresentations),
          concatMap(action => this.presentationService.getPresentations(action.pageable)
            .pipe(
              map(presentations => {
                this.store.dispatch(setLoadingSpinner({status: false}));
                return PresentationActions.loadPresentationsSuccess({page: presentations});
              }),
              catchError(error => of(PresentationActions.loadPresentationsFailure({error})))
            )
          )
        );
      });
      uploadFile$ = createEffect(() => {
        return this.actions$
          .pipe(
            ofType(PresentationActions.fileUpload),
            concatMap(action =>
              this.fileService.uploadFile(action.presentationId, action.files)
                .pipe(
                  map(bool => PresentationActions.loadPresentations({pageable: new Pageable()})),
                  catchError(error => of(PresentationActions.fileUploadFailure))
                )
            )
          );
      });

As you can see in updatePresentations$ effect I am trying to return the loadPresentations action which is supposed to call loadPresentations$ effect, however this does not work, I guess because I am supposed to return and action that will get resolved by the reducer? How do I make this work?


Solution

  • Your action does not need to be tied to the reducer. We use actions just to trigger effects which would then dispatch actions with reducers. So this should be ok.

    You effects look ok, as mentioned before, there is maybe some place for optimization though. Otherwise I don't see anything suspicious

      updatePresentations$ = createEffect(() => {
        return this.actions$.pipe(
          ofType(PresentationActions.updatePresentation),
          concatMap(action =>
            this.presentationService.updatePresentation(action.presentation).pipe(
              map(presentation => {
                // fileUpload dispatches loadPresentations anyway, is there a need to do it twice?
                if (action.files !== null && action.files.length > 0) {
                  return PresentationActions.fileUpload({ presentationId: presentation.id, files: action.files });
                }
                return PresentationActions.loadPresentations({ pageable: new Pageable() });
              }),
              catchError(error => of(PresentationActions.updatePresentationFailure({ error })))
            )
          )
        );
      });
    
      uploadFile$ = createEffect(() => {
        return this.actions$.pipe(
          ofType(PresentationActions.fileUpload),
          concatMap(action =>
            this.fileService.uploadFile(action.presentationId, action.files).pipe(
              map(bool => PresentationActions.loadPresentations({ pageable: new Pageable() })),
              catchError(error => of(PresentationActions.fileUploadFailure))
            )
          )
        );
      });
    
      loadPresentations$ = createEffect(() => {
        return this.actions$.pipe(
          ofType(PresentationActions.loadPresentations),
          concatMap(action =>
            this.presentationService.getPresentations(action.pageable).pipe(
              // Use switch map to dispatch more actions at once
              swithcMap(presentations => [
                setLoadingSpinner({ status: false }),
                PresentationActions.loadPresentationsSuccess({ page: presentations }),
              ]),
              catchError(error => of(PresentationActions.loadPresentationsFailure({ error })))
            )
          )
        );
      });
    

    Same with