ngrx postpone an action in effect until another action is dispatched

Viewed 703

Is it possible to postpone handling an action in effect until some other action is dispatched? here the exact scenario:

1.dispatch action A1

2.effect handle A1 and request an http call

3.before http call is finished action B1 is dispatched

4.effect want to handle action B1 but it needs a part of store which is not ready until the "A1 http request" is completed

I handle it by a variable inside effect class, but I'm looking for a cleaner solution. here is my code

  numberOfDelaying=0;
  handleB1 = createEffect(() => {
    return this.actions.pipe(
      ofType(DashboardActions.B1),
      withLatestFrom(this.store.pipe(select(someSelector))),
      concatMap(([action, stateSlice]) => {
        //need the state slice which is not ready until A1 is completed
        if(!stateSlice && this.numberOfDelaying<10){ // here i check if the data is not ready dispatch B1 after 100 ms
          this.numberOfDelaying++;
          return of(DashboardActions.B1()).pipe(delay(100));
        }
        else if (stateSlice){
          // do some async call
        }
        else {
          return of(DashboardActions.Error())
        }

      })
    );
  });
2 Answers

This could be a way to do it:

handleB1 = createEffect(
  () => this.actions.pipe(
    ofType(DashboardActions.B1, DashboardActions.A1),
    pairwise(),
    switchMap(
      // Wait for `A1` to send the new state only if the prev action was `A1`
      ([prevAction, crtAction]) => 
        prevAction.type === DashboardActions.A1.type 
          ? this.store.pipe(
            select(someSelector), 
            skip(1) // Wait until the `A1` sends its new state
            first(),
            mapTo(crtAction)
          )
          : of(crtAction)
    )
    /* ... */
  )
)

You can give multiple actions to ofType: ofType(DashboardActions.B1, DashboardActions.A1, ...),.

As you mentioned, in handleA1 you'll dispatch B1 action before the http call is made. The A1 action is also intercepted by handleB1, so when B1 is dispatched from handleA1, in handleB1 the pair [prevAction, crtAction] would be this: [A1, B1].

Then, you're selecting the A slice, but you'll want to skip the existing state as it will be the old one and proceed only when the new state will arrive, which is when the asynchronous operation handleA1 would've finished.

handleB1 = createEffect(() => {
    return this.actions.pipe(
      ofType(DashboardActions.B1),
      withLatestFrom(this.store.pipe(select(someSelector))),
      filter(([action, stateSlice]) => stateSlice.updated)
      ...

Your filter would look for some specific indication that the data has updated. Depending on the usage, if I need to know that something has completed I attach a token, a UUID for example, and watch for it in a filter. It could be included in B1 payload.

Would it work to dispatch B1 in the A1 effect? Something like this:

handleA1 = createEffect(() =>
   this.actions.pipe(
      ofType(DashboardActions.A1),
      mergeMap(action => apicall(action.payload)
           .pipe(
              map(resp => DashboardActions.B1),
              catchError(err => do something with error)
    )
)
)
Related