Subscribing to a nested Observable
I'd do it like the following:
function mockRequest() {
return Observable.of('[{"id": 1}, {"id": 2}, {"id": 3}]');
}
function otherMockRequest(id) {
return Observable.of(`{"id":${id}, "desc": "description ${id}"}`);
}
class ItemsService {
fetchItems() {
return mockRequest()
.map(res => JSON.parse(res))
.concatAll()
.mergeMap(item => this.fetchItem(item));
}
fetchItem(item: Item) {
return otherMockRequest(item.id)
.map(res => JSON.parse(res));
}
}
let service = new ItemsService();
service.fetchItems().subscribe(val => console.log(val));
See live demo: http://plnkr.co/edit/LPXfqxVsI6Ja2J7RpDYl?p=preview
I'm using a trick with .concatAll()
to convert an array of Objects such as [{"id": 1}, {"id": 2}, {"id": 3}]
into separate values emitted one by one {"id": 1}
, {"id": 2}
and {"id": 3}
(as of now it's an undocumented feature). Then I use mergeMap()
to fetch their content in a separate request and merge it's result into the operator chain.
This plnkr example prints to console:
{ id: 1, desc: 'description 1' }
{ id: 2, desc: 'description 2' }
{ id: 3, desc: 'description 3' }
The problem you likely encountered is that you did not flatten enough.
flatMap
or mergeMap
will flatten Observables
, Promises
, Arrays
, even generators
(don't quote me on that last one), just about anything you want to throw at it.
So when you do .flatMap(items => items.map(item => this.fetchItem(item))
, you are really just doing Observable<Array<Item>> => Observable<Observable<Item>>
When you just do map
you are doing Observable<Array<Item>> => Observable<Array<Observable<Item>>>
.
What you need to do is first flatten out the Array and then flatten out each request:
class ItemsService {
fetchItems() {
return this.http.get(url)
.map(res => res.json())
// Implicitly map Array into Observable and flatten it
.flatMap(items => items)
// Flatten the response from each item
.flatMap((item: Item) => this.fetchItem(item));
}
}
Now the above works if you don't mind getting each item response individually. If you need to get all of the items then you should use forkJoin
on all the inner values, but you would still need flatMap
in order to flatten the resulting inner value:
fetchItems(): Observable<Response[]> {
return this.http.get(url)
.map(res => res.json())
.flatMap(items => {
const requests = items.map(item => this.fetchItem(item));
return Rx.Observable.forkJoin(requests);
});
}