Kalpesh Patel
Kalpesh Patel

Reputation: 269

How to implement "Next" and "Previous" pagination with Angular + NgRx

I am working on Angular project which uses NgRx to cache data. On a Submit button event, I need to fetch 20 records from server and display it. Now, I need to implement pagination(Next and Previous only) for next iteration. When I make a next request, I want to append the response to cache(NgRx store) so that I don't have to make request to backend for "Previous" page. In short, If cache already contains records, I don't want to make request to server. Where and how should I implement pagination logic? In a component or using Router Resolver?

My current implementation(not working) is:

constructor(
  private store: Store<AppState>,
  private activatedRoute: ActivatedRoute,
  private router: Router
  ) {
  this.guestAds$ = this.store.select(guestAds); 
  this.activatedRoute.queryParams.subscribe((params: any) => {
   if (Object.keys(params).length) {
     this.queryParams = params;
     this.getAds();
   }
 });
}

onSubmit() {
  const form = this.searchForm.value;
  const queryParams = {
    searchTerm: form.searchTerm,
    category: form.category,
    subCategory: form.subCategory,
    limit: 20,
    startAt: 0,
    prevKeys: ''
  };
  this.router.navigate(['/'], { queryParams });
}

getAds() {
  this.guestAds$
    .pipe(
      tap(ads => {
        if (ads.length) {
          ads.forEach(ad => {
            if (ad.createdAt === Number(this.queryParams.startAt)) {
              console.log('Data alreadyd available');
              /* some logic */
            } else {
              this.store.dispatch(LoadAds({payload: this.queryParams}));
            }
          });
        }
      }),
    ).subscribe(/* I dont know if this is required?! */);
}

My NgRx Effect looks like this:

loadAds$ = createEffect(() =>
  this.actions$.pipe(
    ofType(GuestAdsActions.LoadAds),
    mergeMap((action) => this.guestAdsService.getAds(action.payload)
      .pipe(
        map((payload: any) => payload.data),
        map((payload: Ad[]) => GuestAdsActions.LoadAdsSuccess({ payload }),
        catchError(() => of(GuestAdsActions.LoadAdsSuccess({ payload: []}))
        )
      )
    )
  )));

My service looks like this:

getAds(queryParams: QueryParams) {
  console.log(queryParams);
  return this.http.get(`${this.BASE_URL}/ads`, { params: { ...queryParams } });
}

I am calling getAds() method based on router query parameters change listener. What am I doing wrong? I would really appreciate if someone could help me on this. Thank you!

Edit: Added Effect and Service

Upvotes: 2

Views: 1867

Answers (1)

Eldar
Eldar

Reputation: 10790

Well it will be complicated but i will try to describe.

  1. Change your state to save your GuestAd from Array to Map
export interface State {
... // other properties
guestAds : Map<string,Array<GuestAd>>;

}
  1. Change your reducer to handle map operations :
... // some other reducer cases
case GuestAdsActions.LoadAdsSuccess:
      const key = JSON.stringify(action.payload); // since your parameter object is complex better using string equality.
      if (state.guestAds.has(key )) { // if exists do not modify 
        return state;
      } else {
        const cloneSet = new Map(state.guestAds);
        cloneSet.set(key, action.data);
        return {...state, guestAds: cloneSet};
      }
  1. Add another selector that accepts parameters
export const selectMappedGuestAds = createSelector(guestAds, (mappings, props) => mappings.get(JSON.stringify(props.payload));
  1. Modify effects to check if data available :
loadAds$ = createEffect(() =>
  this.actions$.pipe(
    ofType(GuestAdsActions.LoadAds),
    withLatestFrom(this.store.select(guestAds), (action,data)=>{
     const key = JSON.stringify(action.payload); // again we need to stringify
     return data.has(key ) && data.get(key).length > 0 ? null : action;
}), // check store if has it? if has cached data return null 
fiter(action=> !!action), // if result not null then we need to request it from server    
    switchMap((action) => this.guestAdsService.getAds(action.payload)
      .pipe(
        map((payload: any) => payload.data),
        map((payload: Ad[]) => GuestAdsActions.LoadAdsSuccess({ payload }),
        catchError(() => of(GuestAdsActions.LoadAdsSuccess({ payload: []}))
        )
      )
    )
  )));
  1. Finally change your store request into parametered one :
this.store.select(selectMappedGuestAds,{payload:queryParams}); 

Upvotes: 0

Related Questions