youri
youri

Reputation: 1160

How to avoid nested rxJS subscription

I started a new Angular 7 project in which I need to call an API to get a list of results. Then if a specific result is in the list I select it, if not I create it calling another API endpoint. All of this stuff should be done into the ngOnInit() method.

To simplify the problem, I have the following code:

const subject = new BehaviorSubject(null);
subject.next(6);

const res$ = subject.pipe(
  mergeMap(() => getValues()),
  tap((res) => {
    const exists = res.find(x => subject.value === x) || null;
    if (exists === null) {
      // TODO: call the getNew() function, but I don't want nested call
    } else {
      // Do nothing
    }
  })
);

// res$ should contains [1, 2, 3, 4, 5, 6]
res$.subscribe(x => console.log(x));

function getValues() {
  // Mocking the API call
  return of([1, 2, 3, 4, 5]);
}

function getNew() {
  // Mocking the API call
  return of(6);
}

At the end, the res$ observable should contains all the array (with the added value) and I want to avoid nested subscription.

Thanks a lot!

Upvotes: 2

Views: 274

Answers (2)

Daniel Gimenez
Daniel Gimenez

Reputation: 20654

Two things:

  1. Don't count on having access to the subject's value. Instead you can you the alternative form of mergeMap which allows you to create a select the value from the source and new stream. In this case I use it to return a new item down the stream with both the source and value from the inner observable.

  2. You can use just another mergeMap to return either the original result or the result of the new api call by wrapping the orignal result in of to create a new inner observable.

const res$ = subject.pipe(
  mergeMap(x => getValues(), (orig, res) => ({ orig, res })),
  mergeMap(({ orig, res }) => {
    const exists = res.find(x => x === orig) || null;
    return (exists) 
      ? of(res)
      : getNew().pipe(map(x => [... res, x]))
  })
);

Stackblitz

Upvotes: 2

SnorreDan
SnorreDan

Reputation: 2920

One way of solving this could be to split this up into two observables, and then combine them againg with combineLatest. We will use the resFirst$ observable twice, and filter it based on if it is null or not.

import { combineLatest } from 'rxjs';

---

const subject = new BehaviorSubject(null);
subject.next(6);

const resFirst$ = subject.pipe(
    mergeMap(() => getValues()),
    map((res) => res.find(x => subject.value === x) || null)
);

const resSecond$ = resFirst$.pipe(
    filter(exists => exists === null), <--- FILTERING FOR IS NULL
    mergeMap(() => getNew()) <--- SWITCHMAP COULD ALSO BE USED HERE DEPENDING ON WHAT YOU WANT
);

const final$ = combineLatest(resFirst$, resSecond$)
    .pipe(filter(exists => exists !== null)) <--- FILTERING FOR NOT NULL
    .subscribe(x => console.log(x));

function getValues() {
    // Mocking the API call
    return of([1, 2, 3, 4, 5]);
}

function getNew() {
    // Mocking the API call
    return of(6);
}

Upvotes: 0

Related Questions