Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxJS iif arguments are called when shouldn't

I want to conditionally dispatch some actions using iif utility from RxJS. The problem is that second argument to iif is called even if test function returns false. This throws an error and app crashes immediately. I am new to to the power of RxJS so i probably don't know something. And i am using connected-react-router package if that matters.

export const roomRouteEpic: Epic = (action$, state$) =>
  action$.ofType(LOCATION_CHANGE).pipe(
    pluck('payload'),
    mergeMap(payload =>
      iif(
        () => {
          console.log('NOT LOGGED');
          return /^\/room\/\d+$/.test(payload.location.pathname); // set as '/login'
        },
        merge(
          tap(v => console.log('NOT LOGGED TOO')),
          of(
            // following state value is immediately evaluated
            state$.value.rooms.list[payload.location.pathname.split('/')[1]]
              ? actions.rooms.initRoomEnter()
              : actions.rooms.initRoomCreate(),
          ),
          of(actions.global.setIsLoading(true)),
        ),
        empty(),
      ),
    ),
  );
like image 876
E1-XP Avatar asked Jan 08 '19 18:01

E1-XP


3 Answers

A little late to the party, but I found that the role of iif is not to execute one path over the other, but to subscribe to one Observable or the other. That said, it will execute any and all code paths required to get each Observable.

From this example...

import { iif, of, pipe } from 'rxjs';
import { mergeMap } from 'rxjs/operators';

const source$ = of('Hello');
const obsOne$ = (x) => {console.log(`${x} World`); return of('One')};
const obsTwo$ = (x) => {console.log(`${x}, Goodbye`); return of('Two')};

source$.pipe(
  mergeMap(v =>
    iif(
      () => v === 'Hello',
      obsOne$(v),
      obsTwo$(v)
    ))
).subscribe(console.log);

you'll get the following output

Hello World
Hello, Goodbye
One

This is because, in order to get obsOne$ it needed to print Hello World. The same is true for obsTwo$ (except that path prints Hello, Goodbye).

However you'll notice that it only prints One and not Two. This is because iif evaluated to true, thus subscribing to obsOne$.

While your ternary works - I found this article explains a more RxJS driven way of achieving your desired outcome quite nicely: https://rangle.io/blog/rxjs-where-is-the-if-else-operator/

like image 128
Sai Avatar answered Oct 23 '22 17:10

Sai


Ok, i found an answer on my own. My solution is to remove iif completely and rely on just ternary operator inside mergeMap. that way its not evaluated after every 'LOCATION_CHANGE' and just if regExp returns true. Thanks for your interest.

export const roomRouteEpic: Epic = (action$, state$) =>
  action$.ofType(LOCATION_CHANGE).pipe(
    pluck<any, any>('payload'),
    mergeMap(payload =>
      /^\/room\/\d+$/.test(payload.location.pathname)
        ? of(
            state$.value.rooms.list[payload.location.pathname.split('/')[2]]
              ? actions.rooms.initRoomEnter()
              : actions.rooms.initRoomCreate(),
            actions.global.setIsLoading(true),
          )
        : EMPTY,
    ),
  );
like image 35
E1-XP Avatar answered Oct 23 '22 18:10

E1-XP


If you use tap operator inside observable creation(because it returns void), it will cause error as below

Error: You provided 'function tapOperatorFunction(source) {
return source.lift(new DoOperator(nextOrObserver, error, complete));
}' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

Remove the tap and put the console in the subscribe().

I have created a stackblitz demo.

like image 30
KiraAG Avatar answered Oct 23 '22 19:10

KiraAG