Home > OS >  Angular NGRX: Use withLatestFrom to call api only once. However, 'loader' is still set to
Angular NGRX: Use withLatestFrom to call api only once. However, 'loader' is still set to

Time:11-09

I created the following code, trying to ensure the api is not called again when the state is set, and avoid using subscriptions in the component.

loadPackingList$ = createEffect(() => {
   return this.actions$.pipe(
     ofType(PackingPageActions.loadPackingList),
     switchMap((action) =>
         of(action).pipe(
            withLatestFrom(this.store.select(selectPackingList)),

            filter(([action, list]) => !list || list.length === 0),

            switchMap(([action, latest]) =>
               this.packingService.getPackingList(action.request).pipe(
               map((list) => PackingApiActions.loadPackingListSuccess({ list })),
               catchError((errors) =>
                   of(PackingApiActions.loadPackingListFail({ errors }))
             )
           )
         )
       )
     )
   );
});

This more or less works (the api is not called again) and is based amongst others on: How to send request to the server only once by using @NgRx/effects

However, the loading property is still set to true again, which should not happen:

export const packingReducer = createReducer(
  initialState,
  on(PackingPageActions.loadPackingList, (state) => ({
   ...state,
   loading: true,
})),

component's OnInit:

this.store.dispatch(
  PackingPageActions.loadPackingList({ request: this.PACKING_REQUEST })
);

Does anybody have an idea how to fix this?

Thanks!

CodePudding user response:

first you can clean up the stream a bit:

loadPackingList$ = createEffect(() => {
   return this.actions$.pipe(
     ofType(PackingPageActions.loadPackingList),
     withLatestFrom(this.store.select(selectPackingList)),
     filter(([action, list]) => !list || list.length === 0),
     switchMap(([action, latest]) =>
       this.packingService.getPackingList(action.request).pipe(
         map((list) => PackingApiActions.loadPackingListSuccess({ list })),
         catchError((errors) =>
           of(PackingApiActions.loadPackingListFail({ errors }))
         )
       )
     )
   );
});

don't need the external switchMap into of, just redundant.

second, you'll need to also put a similar check into your reducer to make sure it doesn't get set unless the effect will actually go:

export const packingReducer = createReducer(
  initialState,
  on(PackingPageActions.loadPackingList, (state) => ({
   ...state,
   loading: !state.whatever.the.path.to.the.list.is?.length,
})),

or instead of filtering, you could just mock responses with the already fetched list:

loadPackingList$ = createEffect(() => {
   return this.actions$.pipe(
     ofType(PackingPageActions.loadPackingList),
     withLatestFrom(this.store.select(selectPackingList)),
     switchMap(([action, latest]) =>
       ((latest && latest.length) ? of(latest) : this.packingService.getPackingList(action.request)).pipe(
         map((list) => PackingApiActions.loadPackingListSuccess({ list })),
         catchError((errors) =>
           of(PackingApiActions.loadPackingListFail({ errors }))
         )
       )
     )
   );
});

you could do this in other ways, like having another action in there that signals the request is actually going out like loadPackingListRequest request that the effect will emit right before the request to signal it's actually happening, and this is what sets the loading state to true, probably cleaner this way since you don't have a hidden dependency on synchronizing code between your reducer and effects. but it's preference at this point.

  • Related