5

I've read the Rx.js repeat Documentation in an effort to find out how I can continue calling an api based upon the response that I receive from the API. I'm calling an API that can only send back 2k records at a time. The API will send back a value for me to send it so that I can continue receiving the records until they return a done value.

So the flow goes as follows:

  1. Make a GET request a query parameter reqMode='':
  2. retrieve response with the last array containing reqMode with a value or done.
  3. If I receive a value then I need to make the same request but send the reqMode parameter with the value.
  4. If i receive done then I'll stop and return all of the records since the first call.

I get the first set of values when subscribing normally, but this would be my attempt after reading the docs, but it doesn't make sense:

getRecords(){
    let url = this.url + 'reqMode=';
    return this.http.get(url)
            .doWhile() //What would I do here
}

When trying to do .doWhile with a Observable that is type Observable<response>. I'm looking for any alternative using Observables for what I need to do.

martin
  • 93,354
  • 25
  • 191
  • 226
Abdullah Rasheed
  • 3,562
  • 4
  • 33
  • 51
  • angular2 ships with rxjs5-beta and based in the documentation you wont be able to use .doWhile... at least not yet: https://github.com/ReactiveX/rxjs/blob/master/MIGRATION.md#operators-renamed-or-removed. Are you looking for alternate solutions? or only for rxjs4? – mrcolombo Sep 30 '16 at 19:49
  • I'm fine with an alternative. – Abdullah Rasheed Sep 30 '16 at 19:52

2 Answers2

9

I don't think repeat() is a good operator for this. If I understand you correctly you want to repeat the HTTP request based on the response of the previous request. Operator repeat() is good if you wanted to repeat the same request multiple times.

I'd use concatMap() and recursively call itself until the reqMode is eqaul to "done":

See live demo: http://plnkr.co/edit/w0DdepslTaKrLSB3aIkA

import {Observable, Subject} from 'rxjs';

const result = new Subject();
const closeBuffer = new Subject();
const buffer = result.buffer(closeBuffer.asObservable());

function sendHttpRequest(reqMode) {
  return Observable.of('{"reqMode":' + reqMode + '}')
    .map(response => JSON.parse(response))
    .concatMap(data => {
      console.log('HTTP Response:', data);
      // Add data to the buffer of results
      result.next(data);

      if (data.reqMode == 'done') {
        // Return an empty value wrapped as an Observable so concatMap can work
        // with it and emit onNext when it completes (which is immediately
        // thanks to the `.of()` operator).
        return Observable.of(null);
      } else {
        // Simulate that the next call returns 'done'
        return sendHttpRequest('"done"');

        // Uncomment this for real usage
        //return sendHttpRequest(data.reqMode);
      }
    });
}

// Subscribe to the buffer where I'll receive the value.
buffer.subscribe(val => console.log('Next: ', val));

// Simulate HTTP request with reqMode = 42
sendHttpRequest(42).subscribe(() => {
  console.log('done');
  // Emit values from the buffer.
  closeBuffer.next(null);
  closeBuffer.complete();
});

I use of() operator to simulate a request and to return a value wrapped as an Observable. I also use Subject to hold all responses that are buffered using buffer() operator. The I subscribe to the buffer to get the final array of responses (If you wrap this code into a function you'll most likely return the buffer where you can subscribe later).

The response is following:

HTTP Response: Object {reqMode: 42}
HTTP Response: Object {reqMode: "done"}
Next:  [Object, Object]

See similar question: Angular 2 + rxjs - how return stream of objects fetched with several subsequent http requests

Community
  • 1
  • 1
martin
  • 93,354
  • 25
  • 191
  • 226
  • looks nice! `recursion` has been a weaker point of mine. It's time to step into a new...I'll try this one right now – Abdullah Rasheed Oct 02 '16 at 11:25
  • @inspired I updated my answer, I didn't realize that you want to stack all responses until `done` which means there might multiple I guess(?) Now it's a little more complicated but I think it's still understandable. – martin Oct 02 '16 at 11:42
  • oh i was trying your method and just pushing each data response to an array in order to gather them all, but I guess that then couples my method to an outside array. – Abdullah Rasheed Oct 02 '16 at 11:52
  • @inspired Yes, that's also an option. I think you need to have a separate variable where you put your results because the Observable chain works with HTTP responses. More _"functional way"_ would be calling `sendHttpRequest()` along with the accumulated array of results (like `sendHttpRequest('"done"', [obj1, obj2])`) but that would made it quite confusing and it's not even necessary. – martin Oct 02 '16 at 11:57
  • I haven't tried the buffer yet, but concatenating the values to an array works great! – Abdullah Rasheed Oct 02 '16 at 12:09
3

So I made an example on how you can do this by using a wrapping observer and using .repeat().

All the logic is in app.component.ts

Checkout this plunker

I left comments in the code, but essentially it will make an http request, it will increment a count, and then will make another request with a different query number. It will repeat until it reaches 5.

You will have to modify it so the "repeat condition" represents your logic.

Hope that helps!

mrcolombo
  • 587
  • 4
  • 19