Подписка на вложенную Observable
У меня есть приложение, которое делает один запрос http, чтобы получить список элементов, а затем делает запрос http для каждого элемента в списке, чтобы получить более подробную информацию о каждом элементе. Эффективно:
class ItemsService {
fetchItems() {
return this.http.get(url)
.map(res => res.json())
.map(items => items.map(this.fetchItem(item)));
}
fetchItem(item: Item) {
this.http.get(`${url}/${item.id}`)
.map(res => res.json());
}
}
Тогда я сделаю что-то вроде itemsService.fetchItems().subscribe(items => console.log(items))
но в конечном итоге я получаю массив наблюдаемых (каждый ответ от fetchItem
). Мне нужно подписаться на каждую из внутренних наблюдаемых, чтобы fetchItem
запрос на самом деле срабатывает.
Я также пытался использовать flatMap
вместо карты, но, похоже, в этом случае тот же результат. Есть ли способ для подписки на вложенные наблюдаемые?
3 ответа
Я бы сделал это следующим образом:
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));
Смотрите живую демоверсию: http://plnkr.co/edit/LPXfqxVsI6Ja2J7RpDYl?p=preview
Я использую трюк с .concatAll()
преобразовать массив объектов, таких как [{"id": 1}, {"id": 2}, {"id": 3}]
на отдельные значения, излучаемые по одному {"id": 1}
, {"id": 2}
а также {"id": 3}
(на данный момент это недокументированная функция). Тогда я использую mergeMap()
получить их содержимое в отдельном запросе и объединить его результат в цепочку операторов.
Этот пример plnkr выводит на консоль:
{ id: 1, desc: 'description 1' }
{ id: 2, desc: 'description 2' }
{ id: 3, desc: 'description 3' }
Проблема, с которой вы, вероятно, столкнулись, заключается в том, что вы недостаточно сглажены.
flatMap
или же mergeMap
сгладит Observables
, Promises
, Arrays
, четное generators
(не цитируйте меня по этому последнему), почти все, что вы хотите бросить в него.
Итак, когда вы делаете .flatMap(items => items.map(item => this.fetchItem(item))
ты действительно просто делаешь Observable<Array<Item>> => Observable<Observable<Item>>
Когда вы просто делаете map
ты делаешь Observable<Array<Item>> => Observable<Array<Observable<Item>>>
,
Что вам нужно сделать, это сначала сгладить массив, а затем сгладить каждый запрос:
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));
}
}
Теперь вышесказанное работает, если вы не возражаете против получения каждого ответа по отдельности. Если вам нужно получить все предметы, вы должны использовать forkJoin
на все внутренние ценности, но вам все равно нужно flatMap
чтобы сгладить полученное внутреннее значение:
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);
});
}
Вы можете разбить массив элементов перед строкой, которая вызывает this.fetchItem
, Вы можете использовать mergeMap для Observable, значение которого является массивом, и каждый элемент будет генерироваться индивидуально.
fetchItems() {
return this.http.get(url)
.map(res => res.json())
.mergeMap(arrItem => this.fetchItem(arrItem));
}
Изменить: я думаю, я должен был предоставить больше объяснений. mergeMap
является синонимом flatMap
в rxjs. Как правило, вы используете flatMap, когда ваша проекционная функция возвращает Observable, но она также сгладит массивы, поэтому вызов mergeMap будет генерировать каждый элемент отдельно, я думал, что это то, чего хотел достичь OP. Я также понял, что вы можете объединить mergeMap
звонок и последний map
звоните, потому что прогноз для mergeMap
будет вызываться для каждого элемента в массиве, я внес изменения в коде выше.