Václav Zeman
Václav Zeman

Reputation: 616

Possible to determine when epics finish in Redux Observable?

I'm new to RxJS so sorry if it doesn't make much sense.

Let's say I want to have a reusable epic for fetching a user that will be invoked by action from an app load epic.

Oversimplified example:

const getUserEpic = action$ =>
  action$.pipe(
    ofType(GET_USER_REQUEST),
    switchMap(action => from(service.fetchUser(action.userId).pipe(
     mapTo({ type: GET_USER_SUCCESS })))
    ),
  );

const appLoadEpic = action$ => 
 action$.pipe(
    ofType(LOAD_APP_REQUEST),
    map(() => of({ type: GET_USER_REQUEST }, { type: SOME_OTHER_REQUEST }))
  );

What if I wanted to call LOAD_APP_SUCCESS after all of the invoked epics (getUser etc.) finish? It'd be great if it could be done in the appLoadEpic but I'm afraid it's not possible.

Upvotes: 0

Views: 464

Answers (1)

paulpdaniels
paulpdaniels

Reputation: 18663

The way I would suggest doing it is combining the the individual epics into a "meta"-epic. That is, you can use the individual streams to listen to their individual events and the propagate them when all the merged streams are completed.

const getUserEpic = action$ => ... 
const someOtherEpic = action$ => ...
// Creates an epic that merges all the results from each provided epic
const initializationEpic = combineEpics(getUserEpic, someOtherEpic)

const appLoadEpic = (action$, state$) => {
  // Runs the new epic with the action and state values.
  const onLoad$ = initializationEpic(action$, state$).pipe(
    endWith({type: LOAD_APP_SUCCESS})
  )

  // Listen for the load app request before subscribing to the initialization
  action$.pipe(
    ofType(LOAD_APP_REQUEST),
    mergeMapTo(onLoad$),
  )
}

If you are feeling fancy and don't want to have to inject the epics via import, you can also dynamically inject epics The docs detail a way to inject the epic asynchronously, meaning that instead of file injection you could include it as part of the action body during start up, this might make testing a bit easier.

  const appLoadEpic = (action$, state$) => {
    // Listen for the load app request before subscribing to the initialization
    action$.pipe(
      ofType(LOAD_APP_REQUEST),
      // Now the epic is injected during the app loading, and you run it inline 
      // here. This makes it easy to mock it during testing
      mergeMap(({epic}) => epic(action$, state$).pipe(endWith({type: LOAD_APP_SUCCESS}))),
    )
  }

Upvotes: 2

Related Questions