Reputation: 36274
If I have list of items coming in Observable from an api, let's call them list of "bars".
And now, for every bar
I need to fetch a list of items related - let's call them "foo"s. Here's the thing - the api lets you retrieve list of foo
s only when associated bar
's id provided.
So how do I do that? Should I fire a separate request in subscribe | forEach
? or maybe I could create stream of urls and somehow merge them with stream of bars
and do it that way?
You can create wonders using Rx when applied to UI stuff, unfortunately I'm having difficult time using it to work with multiple ajax requests.
Can you show me an example when you have one Observable and for every item you retrieve more data?
for example, if I try doing something like this:
Rx.Observable.fromArray([ { bar:1 }, { bar:2 }, { bar:3 } ])
.map((x)=> {
x.foo = Rx.Observable.fromPromise(grabFoo(x));
return x;
}).subscribe((x)=> console.log(x))
it wouldn't work. so how do I do something like this?
Upvotes: 2
Views: 191
Reputation: 1294
I just did this. I used flatMap.
Here is a redacted form of my working code:
Updated with comments and using Bar and Foo and add listofbars()
// accumulates all bar s and foo s
var data = {bars: [], errors: []};
// return observable of a foo
var getFoo = function (Id, apiKey) {
return Rx.Observable.fromPromise($.ajax({
method: "GET",
url: config.Uri + "/foos/" + Id,
headers: { "Authorization": "Bearer " + apiKey },
})).share();
};
// return Observable of an array of bars
var getBars = function (apiKey) {
return Rx.Observable.fromPromise($.ajax({
method: "GET",
url: config.Uri + "/bars",
headers: { "Authorization": "Bearer " + apiKey },
})).share();
};
// flatten Observable of an array of bar s to Observable of bar s
var listOfBars = function (apiKey) {
return getBars(apiKey)
.flatMap(function (ep) { return (_a = Rx.Observable).of.apply(_a, ep); var _a; });
}
// accumulate foos into data
var getDataForFoo = function (data, bar, apiKey) {
return getFoo(bar.fooId, apiKey)
.map(function (foo) {
bar.foo = foo;
data.bars.push(bar);
return data;
})
.catch(function (ex) {
data.errors.push({ name: { Id: Id, name: Name }, error: ex });
return Rx.Observable.of(data);
});
};
var getDataForBars = function (data, apiKey) {
return listOfBars(apiKey)
.flatMap(function (bar) { return getDataForFoo(data, bar, apiKey); })
.catch(function (ex) {
data.errors.push({ names: { Id: Id }, error: ex });
return Rx.Observable.of(data);
});
};
This flatMap takes arrays of bar and emits each bar separately.
.flatMap(function (ep) { return (_a = Rx.Observable).of.apply(_a, ep); var _a; })
This flatMap takes each bar and makes a new web request using the fooId from the bar.
.flatMap(function (bar) { return getDataForFoo(data, bar, apiKey); })
The above example accumulates the bar s and foo s into a data model. You appear to be asking for a stream of bar that contains the foo. Given the above that would look like:
var barswithfoo = listOfBars(apiKey)
.flatMap(bar => {
return getFoo(bar.fooId, apiKey)
.map(foo => {
bar.foo = foo;
return bar;
});
})
.subscribe();
You can see that this is similar to the answer you posted. flatMap is a map + merge. it expects the map to return an Observable and immediately subscribes to every observable that is returned.
Upvotes: 1
Reputation: 36274
So, to actually make something like that work, I tried this first:
listOfbars.flatMap(x =>
getFoo(x).then(r => {
x.foo = r;
return x;
})
)
.subscribe(x => console.log(x))
it works, although it is painfully slow (because I assume it resolves all promises only in the end), compared to this:
listOfbars.flatMap(x =>
Rx.Observable.fromPromise(getFoo(x))
.map(r => {
x.foo = r;
return x;
})
)
.subscribe(x => console.log(x))
It looks like the second snippet shouldn't even work, surpisingly it does
Upvotes: 0