iLemming
iLemming

Reputation: 36274

Resolving Observables on properties of an Observable element

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 foos 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

Answers (2)

Kirk Shoop
Kirk Shoop

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

iLemming
iLemming

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

Related Questions