Search code examples
angularrxjsngrxngrx-effects

concatLatestFrom loop inside NGRX effect


I have orders/order items that are organized in schedules/schedule items. Each order item corresponds to 1+ schedule items.

I have a function that lets a user approve (or disapprove) an schedule item which will also cause all the other schedule items related to that same order item to be approved. Approving an item might cause the respective schedule to be reordered.

Prior to actually setting the schedule items to approved I need to do a timestamp check to make sure that user was working on the latest data. The general workflow for that is to pass along all 'affected entities' with the respective http request.

What I'm struggeling with now is grabbing all those affected entities from the NGRX store within my effect, specifically I need to grab

  • All scheduleitems in the same schedule as the input item
  • All scheduleitems with the same orderitem as the input item ('split items')
  • All scheduleitems in the same schedules as those split items
  • All corresponding schedules

I have these kind of selectors:

export const getScheduleById = (id: number) =>
  createSelector(getScheduleEntities, (entities) => entities[id]);

export const getScheduleItemsForSchedule = (scheduleId: number) =>
  createSelector(getScheduleItemsAlls, (all) => all.filter(x => x.id === scheduleId));

export const getOtherSchedulesForOrderItem = (scheduleItem) => 
  createSelector(getScheduleItemsAlls, (all) => all.filter(x => x.orderItemId === scheduleItem.orderItemId));

My (incomplete) effect looks like this (approved: boolean):

approveItem$ = createEffect(() =>
  this.actions$.pipe(
    ofType(scheduleItemActions.approveItem),
    concatMap(({ item, approved }) =>
      of({ item, approved }).pipe(
        concatLatestFrom(() => [
          this.store.select(scheduleItemSelectors.getOtherSchedulesForOrderItem(item)),
          this.store.select(scheduleSelectors.getScheduleById(item.scheduleId)),
          this.store.select(scheduleItemSelectors.getScheduleItemsForSchedule(item.scheduleId))
        ]),

        // At this point I would need to do a concatLatestFrom store.select with 'getScheduleById'  
        // and 'getScheduleItemsForSchedule' for all 'splitItems', but I don't know how to that.
        // Subsequent map etc. has to be adjusted of course:
        // I'd combine down to ({ item, approved, allSchedules*, allScheduleItems* })
        // *Separated since right know I distinguish 'object type' in code, not by property and interfaces only exist during dev time.

        map(([{ item, approved }, splitItems, itemSchedule, itemScheduleItems]) =>
          ({ item, approved, splitItems, itemSchedule, itemScheduleItems })
        )
      )
    ),
    switchMap(({ item, approved, splitItems, itemSchedule, itemScheduleItems }) => {
      let affectedEntities: AffectedEntity[] = /* just builds a list with subset-information of the entities (object type, id, timestamp) */

      return this.scheduleItemService.approveItem(item, approved, affectedEntities).pipe(...); 
    })
  )
);

I'm not too familiar with rxjs operators yet and this one is giving me a pretty hard time.

I'd appreciate help and/or resources that cover something similar (I could not find anything, but I also didn't really know any good keywords for searching for this).


Solution

  • So, directed from the comments, I moved the logic to separate selectors.

    It looks somewhat like this now:

    Selectors:

    export const getApproveAllSchedules = (item: ScheduleItem) =>
      createSelector(
        getScheduleAll,
        scheduleItemSelectors.getScheduleItemsForOrderItem(item.orderItemId),
        (all, splitItems) => {
          let scheduleIds = splitItems.map((o) => o.scheduleId);
          return all.filter((x) => scheduleIds.indexOf(x.id) > -1);
        }
      );
    
    //---
    
    export const getApproveAllScheduleItems = (item: ScheduleItem) =>
      createSelector(
        getScheduleItemAll,
        getScheduleItemsForOrderItem(item.orderItemId),
        (all, splitItems) => {
          let schedules = splitItems.map((o) => o.scheduleId);
          return all.filter((x) => schedules.indexOf(x.id) > -1);
        }
      );
    

    Effect:

    approveItem$ = createEffect(() =>
        this.actions$.pipe(
          ofType(scheduleItemActions.approveItem),
          concatLatestFrom(({ item, approved }) => [
             this.store.select(scheduleSelectors.getApproveAllSchedules(item)),
             this.store.select(scheduleItemSelectors.getApproveAllScheduleItems(item)),
          ]),
          switchMap(([{ item, approved }, schedules, scheduleItems]) => {
            let affectedEntities: AffectedEntity[] = /* just builds a list with subset-information of the entities (object type, id, timestamp) */
    
            return this.scheduleItemService.approveItem(item, approved, affectedEntities).pipe(...);
          })
        )
      );