Home > Back-end >  NGRX Effects / RXJS: Loop API Calls and Actions
NGRX Effects / RXJS: Loop API Calls and Actions

Time:03-17

The goal is to loop through an Array of IDs and for each ID make an API Call and dispatch an Action with the result.

My current idea:

someEffect$ = createEffect(() => this.actions$.pipe(
    ofType(someAction),
    withLatestFrom(this.store.select(selectIds)),
    switchMap(([_, ids]) => {
      return forkJoin(ids.map(id =>
        this.service.getData(id))).pipe(
        map(result => {
          return actionSuccess({result});
        }),
        catchError((error: HttpErrorResponse) => {
          return of(actionError({error}));
        })
      )
    })
  ));

This kinda works, but has 2 unwanted behaviours:

  1. actionSuccess gets only dispatched once and with the result of all api calls as an array. I want one dispatch for each API Call.

Replacing forkJoin with merge triggers an action for each ID, but then the API Calls are no longer happening, so there has to be more to it.

  1. forkJoin waits for all API Calls to finish and only then continues. I want to dispatch actionSuccess immediately after every time getData() finishes.

CodePudding user response:

I know you are asking how to dispatch actions looping through an array in the effects but, how about if, for example, let the component handle the looping operation, with that, you will be dispatching for each id a success action.

Component:

loadCustomerById(): void {
  this.store.select(ClientSelectors.selectIds).subscribe((ids) => {
    if (ids.length) {
      for (const id of ids) {
       this.store.dispatch(ClientActions.loadCustomerById({ payload: id }));
      }
    }
  })
}

Effects:

loadCustomerById$ = createEffect(() => this.actions$.pipe(
    ofType(loadCustomerById),  
    // you already get the id from action
    // no need of withLastestFrom  
    mergeMap(({ payload: id }) => 
      this.service.getData(id).pipe(
        map((response) => ClientActions.loadCustomerByIdSuccess({ payload: response })),
        catchError((error) => of(ClientActions.loadCustomerByIdFail({ error })))
      )
    ) 
  ));

CodePudding user response:

Use merge instead of forkJoin - for more info see https://timdeschryver.dev/snippets#multiple-service-calls-from-an-effect

refresh$ = createEffect(() =>
  this.actions$.pipe(
    ofType(CustomerActions.refresh),
    exhaustMap(({ customerIds }) =>
      merge(
        ...ids.map((id) =>
          this.customersService.getCustomer(id).pipe(
            map(CustomerActions.getCustomerSuccess),
            catchError((err) =>
              of(CustomerActions.getCustomerFailed(id, err.message)),
            ),
          ),
        ),
      ),
    ),
  ),
)
  • Related