Reputation: 51
I have a hard time figuring out how to create a recursive loop to api calls with Observables.
Scenario: I call external API, which returns something like this:
{
data: {something, something, something},
next: "url for next set of data"
}
I need to keep on calling the same function to gather all the data into a single object as long as the response has value in the next
.
I managed to do this on another project with Promises where I map the data returned into single array by using the concat()
function but I somehow cannot get my head around understanding how I should do this with Observables
.
Working example with using promises:
getData: function(url, params, headers){
return new Promise((resolve, reject) => {
axios.get(url, {
params: params,
headers: headers,
}).then((response) => {
let responseData = response.data.data[0];
if (response.data.next) {
this.getData(response.data.next, {}).then((resp) => {
for (let dataSet of responseData.dataSets) {
let row = resp.dataSets.find(i => i.variable === dataSet.variable)
if (row) {
dataSet.data = dataSet.data.concat(row.data)
}
}
resolve(responseData);
}).catch((error) => {
reject(error)
})
} else {
resolve(responseData);
}
}).catch(error => {
reject(error)
})
})
}
Upvotes: 4
Views: 3886
Reputation: 1768
Just got into this similar problem today, here's my attempt. I think the hard part is to think correctly about what you're trying to achieve and then find the correct operator to support that.
In this case, from the first observable, we want to expand
that and continue to emit values recursively until complete. What we want to collect at the end is all the emitted values from this observable and that's when I googled the correct keywords and found toArray
to support this case.
Reference: How to collect array of emitted values from Observable.from?
this.getData(endpoint, options).pipe(
expand(({ next }) => {
return next ? this.getData(next, options) : Observable.empty()
}),
toArray(), // wait for the observable to complete and collect all emitted values
)
Upvotes: 1
Reputation: 51
End solution that worked for me:
let obs = this.getData(endpoint, options).pipe(
expand(({ next }) => {
// This could be oneliner but I had to alter options for the calls after the first one for my own case
return next ? this.getData(next, options) : Observable.empty()
}),
concatMap(({data}) => data)
)
obs.subscribe(
data => mapthedata(data),
error => error,
complete => {
// do something with the mapped data
}
)
function mapthedata(data) {
// here you should combine the data results into one, f.ex pushing to local variable
}
Upvotes: 1
Reputation: 8478
You can use the .expand()
operator. The terminating condition for this recursion is when the next
property is falsy
. Use a ternary operator and the code is just one liner:
expand(({data, next}) => next ? getData(next): Observable.empty() )
.subscribe(result => console.log(result));
Here is the working JSBin. I mocked quite a few stuffs but it should be quite trivial.
Upvotes: 4