Search code examples
angularngrxngrx-store

Combine selector or retrieve action parameter in effect pipeline


Given the following action:

export const UpsertChartMember = createAction(ClubDetailActionTypes.UPSERT_CHART_MEMBER, props<{ clubId: number, member: IChartMember}>());

I'd like to retrieve the clubId action parameter in the map operator to dispatch another action:

@Effect({ dispatch: false })
  upsertChartMember = this.actions$.pipe(
    ofType(UpsertChartMember),
    mergeMap(action => {
      const member = action.member;
      return this.chartMemberDataService.update(member);
    }),
    map(member => {
      this.messageService.success(this.constService.SAVE_SUCCESS);
      this.clubDetailStore.dispatch(UpsertChartMemberSuccess({ member: member }));

      // retrieve action.clubId here?! O_o?
      // TODO: dispatch here another action based upon action clubId

    }),
    catchError((error) => {
      this.messageService.error(this.constService.SAVE_FAILURE);
      this.clubDetailStore.dispatch(UpsertClubChartFailure({ err: error }));
      return throwError(error);
    }),
  );

as alternative I have already in the feature store module the clubId accessible by a dedicated selector:

export const getCurrentClub$ = createSelector(getclubDetailState$, (state: ClubDetailState) => state.form);

but I don't understand how to combine the selector and the result of ajax call in my effect...

What I am doing wrong?


Solution

  • You can do a switchMap to grab it from the store using the selector you already have and a combineLatest to keep pass the member info forward along with the club grabbed from the store:

    @Effect({ dispatch: false })
      upsertChartMember = this.actions$.pipe(
        ofType(UpsertChartMember),
        mergeMap(action => {
          const member = action.member;
          return this.chartMemberDataService.update(member);
        }),
        switchMap(member => combineLatest([
          of(member),
          this.store.pipe(select(getCurrentClub$)),
        ])),
        map(([member,club]) => {
          this.messageService.success(this.constService.SAVE_SUCCESS);
          this.clubDetailStore.dispatch(UpsertChartMemberSuccess({ member: member }));
    
          // now you have your club to do whatever you want to
          console.log({club});
    
        }),
        catchError((error) => {
          this.messageService.error(this.constService.SAVE_FAILURE);
          this.clubDetailStore.dispatch(UpsertClubChartFailure({ err: error }));
          return throwError(error);
        }),
      );
    

    BTW, the code above can return both the actions directly. By using switchMap. Instead of map, you can use another chain:

    @Effect()
      upsertChartMember = this.actions$.pipe(
        ofType(UpsertChartMember),
        mergeMap(action => {
          const member = action.member;
          return this.chartMemberDataService.update(member);
        }),
        switchMap(member => combineLatest([
          of(member),
          this.store.pipe(select(getCurrentClub$)),
        ])),
        switchMap(([member, club]) => {
          this.messageService.success(this.constService.SAVE_SUCCESS);  
          return [
            UpsertChartMemberSuccess({ member: member }),
            PutYourOtherActionHere({club...})
          ];
        }),
        catchError((error) => {
          this.messageService.error(this.constService.SAVE_FAILURE);
          this.clubDetailStore.dispatch(UpsertClubChartFailure({ err: error }));
          return throwError(error);
        }),
      );
    

    [Edited by the question author]: Or, by keeping most of your code right as it is:

    @Effect({ dispatch: false })
      upsertChartMember: any = this.actions$.pipe(
        ofType(UpsertChartMember),
        mergeMap(action => {
          const member = action.member;
          let obs: Observable<IChartMember>;
          if (member.id > 0) {
            obs = this.chartMemberDataService.update(member);
          } else {
            obs = this.chartMemberDataService.add(member);
          }
          return combineLatest(of(action), obs);
        }),
        map((args) => {
          this.messageService.success(this.constService.SAVE_SUCCESS);
          this.clubDetailStore.dispatch(UpsertChartMemberSuccess({ member: args[1] }));
          this.clubDetailStore.dispatch(GetClubOrganizationCharts({clubId: args[0].clubId}));
        }),
        catchError((error) => {
          this.messageService.error(this.constService.SAVE_FAILURE);
          this.clubDetailStore.dispatch(UpsertClubChartFailure({ err: error }));
          return throwError(error);
        }),
      );