neonguru
neonguru

Reputation: 759

Better Observable typescript pattern for if x of Observable<X> else y of Observable<Y>

I have 2 observables and I want to get a value from X if it exists, else get it from Y. The following works for me, but I am looking for a more succinct pattern.

import {of} from 'rxjs';
import {map, mergeMap} from 'rxjs/operators';
import {getX, getY} from './getXY';

// getX(id: string): Observable<X>; // gets an X asyncronously
// getY(id: string): Observable<Y>; // gets a Y asyncronously

let id = "abc";

// get an X, or a Y if not X, that matches the id
getX(id).pipe(mergeMap((x: X) => {
  if (x) {
    return of(x);
  } else {
    return getY(id).pipe(map((y: Y) => {
      if (y) {
        return y;
      }
    }));
  }
})).subscribe((xy: X | Y) => { ... }

It seems to me that the of(x) is going in the wrong direction - that perhaps instead of mergeMap, there is a way to conditionally extract the Y from the getY without the need for the of(x).

Ideally I will write a getXY that does all that and returns an Observable<X | Y> that can be subscribed to.


I create a codepen with the original example and cartant's answer. Notice that when getX returns 'x', cartant's still calls getY - that is why I did not mark his answer as the solution. Feel free to fork and add your own solution.

Upvotes: 2

Views: 277

Answers (2)

neonguru
neonguru

Reputation: 759

The simplest solution with operators, that doesn't call getY when getX succeeds is:

function getXY(id: string): Observable<X|Y> {
  return getX(id).pipe(mergeMap((x: X) => {
    if (x) {
      return of(x);
    } else {
      return getY(id);
    }
  }));
};

I have updated the codepen with this getXY function included. As well as getXYcartant - and the following getXYnew:

Observable.create is another possible answer since it doesn't call getY if X is null:

function getXYnew(id: string): Observable<X|Y> { 
  return Observable.create(observer => {
    getX(id).subscribe(x => {
      if (x) {
        observer.next(x);
        observer.complete();
      } else {
        getY(id).subscribe(y => {
          observer.next(y);
          observer.complete();
        });
      }
    });
  });
};

But this, like the other more performance optimal options, creates another Observable.

Upvotes: 0

cartant
cartant

Reputation: 58410

You could implement your getXY function like this:

function getXY(id: string /* or whatever */) {
  return concat(
    getX(id).pipe(filter(x => Boolean(x))),
    getY(id).pipe(filter(y => Boolean(y)))
  ).pipe(take(1));
}

The function's return type will be inferred to be Observable<X | Y> and the implementation is a little less imperative.

Using concat will ensure that the observable returned by getY will only be subscribed to if the observable returned by getX either completes without emitting or emits an falsy value.

Upvotes: 4

Related Questions