1
votes

We have an API that will respond with a boolean hasMore and a token. If hasMore is true, we can do another API call using that token to get the next set of results. This process needs to be repeated until hasMore is false. Then, the whole resulting list needs to be emitted as one single list and the observable needs to be completed.

I've managed to implement this using a BehaviorSubject:

function getAll() {
  const subject = new BehaviorSubject(undefined);

  const obs = subject
    .asObservable()
    .pipe(
      flatMap(token => getRes(token)
        .pipe(
          tap(result => result.hasMore ? subject.next(result.token) : subject.complete())
        )
      ),
      map(result => result.items),
      reduce((acc, items) => acc.concat(items), [])
    );

  return obs;
}

(where getRes() returns an observable that does an API call with the provided token and emits the API response body as JSON)

See https://rxviz.com/v/7J245RDo

However, I was wondering if there was a pure observable way of doing this, without using a BehaviorSubject. I tried playing around with repeatWhen, but couldn't figure out how to change the input data, and yet map to the response data.

1

1 Answers

2
votes

What you look for is called the expand() operator. Basically it allows you to recursion based on the input it receives

doRequest()
  .expand((resultPage) => resultPage.hasMore ? doRequest(resultPage.token) : Observable.empty())
  .subscribe((page) => console.log(page));

Every page of results is emitted through the expand() operator to your subscription. In the expand you can recurse to the next page if needed or return an empty observable to signal end of recursion.