4

I have an action that I want to use to initialise my app, I want to create an epic for this action and then fire multiple other actions off the back of this, wait for them to all complete and there fire another action. I had a look at other questions and it's pretty similar to this one

I've tried this approach and for me, it doesn't work, it fires the APP_INIT action then but then fails to fire any of the other actions in the sequence. Is anyone able to help?

import { of } from 'rxjs';
import { mergeMap, zip, concat, mapTo } from 'rxjs/operators';
import { ofType } from 'redux-observable';
import { firstAction, secondAction } from 'actions';

export default function appInit (action$) {
  return (
    action$.pipe(
      ofType('APP_INIT'),
      mergeMap(() =>
        concat(
          of(firstAction()),
          of(secondAction()),
          zip(
            action$.ofType('ACTION_ONE_COMPLETE'),
            action$.ofType('ACTION_TWO_COMPLETE')
          ).mapTo(() => console.log('complete'))
        )
      )
    )
  );
}
woolm110
  • 1,194
  • 17
  • 27
  • You say `INIT` action but in your example you have `APP_INIT` – martin Feb 10 '19 at 21:47
  • Just a typo, i'll update. The `APP_INIT` action is firing and I can see this in Redux Dev tools, it's just not doing anything after that – woolm110 Feb 10 '19 at 21:47

2 Answers2

8

Turns out my code was pretty much okay in the first instance, the main cause was that I was importing concat from rxjs/operators when I should have been importing from rxjs directly, it took me hours to realise but it now works.

Full code below for anyone that it might help.

import { of, concat, zip } from 'rxjs';
import { mergeMap, map, take } from 'rxjs/operators';
import { ofType } from 'redux-observable';

import { appInitialisationComplete, APP_INITIALISATION } from 'client/actions/app/app';
import { actionOne, ACTION_ONE_COMPLETE } from 'client/actions/action-one/action-one';
import { actioTwo, ACTION_TWO_COMPLETE } from 'client/actions/action-two/action-two';

/**
 * appInitialisationEpic
 * @param  {Object} action$
 * @return {Object}
 */
export default function appInitialisationEpic (action$) {
  return (
    action$.pipe(
      ofType(APP_INITIALISATION),
      mergeMap(() =>
        concat(
          of(actionOne()),
          of(actioTwo()),
          zip(
            action$.ofType(ACTION_ONE_COMPLETE).pipe(take(1)),
            action$.ofType(ACTION_TWO_COMPLETE).pipe(take(1))
          )
            .pipe(map(() => appInitialisationComplete()))
        )
      )
    )
  );
}
woolm110
  • 1,194
  • 17
  • 27
  • 1
    Holllllly heck!!! Same here!!! I was doing the exaaaact same thing "fire action and wait for actions back". And thennnn after reading this I realized I also imported `concat` from `rxjs/operators`. Why on earth is there one in both packages if one is wrong? Super thanks for sharing this solution. – Noitidart Apr 21 '19 at 03:48
  • This is what I had been looking for days. The return zip inside mergeMap saves my day. Many thanks – Nguyen Ho Jun 12 '23 at 15:38
0

combineLatest is what you want which will only emit when all observables have emitted.

const { combineLatest, of } = rxjs;
const { delay } = rxjs.operators;

combineLatest(
  of(1),
  of(2).pipe(delay(2000)),
  of(3).pipe(delay(1000))
).subscribe(([a,b,c]) => {
  console.log(`${a} ${b} ${c}`); // Will take 2 seconds as that is when all have emitted
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>
Adrian Brand
  • 20,384
  • 4
  • 39
  • 60