Observable коллекции Observable в шаблоне

Поскольку название определенно не соответствует моим намерениям, вот что я пытаюсь сделать.

Я хочу отобразить список элементов в представлении, которое я получаю асинхронно от серверной части (у меня есть служба с методом fetchItems() для их извлечения с типом возврата Observable<Item[]>).
Я инициализирую этот список в компоненте конструктор, установив this.items = this.itemService.fetchItems();.
Я отображаю этот список элементов в представлении, выполнив *ngFor="let item of items | async".

Пока все хорошо, все работает как надо.

У каждого элемента есть поле subItemId. Я хочу использовать этот ключ для отображения в вышеупомянутом списке фактического объекта подэлемента типа SubItem. Я могу получить этот элемент через this.subItemService.findById(subItemId), который возвращает Observable<SubItem>.

Как мне это сделать?

Очевидно, что у меня не может быть метода компонента, такого как getSubItem(item: Item): SubItem, который я вызываю из шаблона, так как он будет вызываться снова и снова.
Я не хочу модифицировать метод itemService.fetchItem(), чтобы жадно загружать подэлементы, поскольку они могут не быть нужным везде, где они используются.

Ответ может быть очевиден, но я работаю с Angular (6) всего пару дней.


person Vlad Vidac    schedule 28.05.2018    source источник
comment
Вы не хотите жадно загружать подэлементы, но каково условие, когда вам нужно загрузить подэлемент? Это пользовательское событие?   -  person sabithpocker    schedule 28.05.2018
comment
В текущем компоненте они требуются всегда, но сервис для получения основных элементов используется и в других местах.   -  person Vlad Vidac    schedule 28.05.2018


Ответы (2)


Вы можете реконструировать родительские дочерние отношения элемента > подэлемента, используя операторы rxjs. Вот пример:

export class AppComponent {
  name = 'Angular 6';
  itemsWithSubItems: Observable<any>;
  subItems: Observable<any[]>;
  getItems(): Observable<{ subItemId: number, itemName: string, subItem?: any }[]> {
    return of([{ subItemId: 1, itemName: 'One'}, { subItemId: 2, itemName: 'Two' }, { subItemId: 3, itemName: 'Three' }, { subItemId: 4, itemName: 'Four' }])
      .pipe(shareReplay());
  }

  constructor() {
    this.itemsWithSubItems = this.getItems().pipe(
        flatMap(items => from(items)),
        switchMap(item => this.getSubItemById(item.subItemId), 
          (outerValue, innerValue, outerIndex, innerIndex) => {
            outerValue.subItem = innerValue;
            return outerValue
          }),
        toArray());
    this.itemsWithSubItems.subscribe(x => console.log('x', x))
  }

  getSubItemById(subItemId: number): Observable<{name: string}> {
    return of({ name: `${subItemId}-SubItem` });
  }
}
person sabithpocker    schedule 28.05.2018

Вы использовали async в шаблоне, что означает отложенную загрузку привязки данных для шаблона. Таким образом, вы ничего не можете сделать, кроме как снова использовать async для дочернего элемента.

<div *ngFor="let item of items | async">
    <span>{{(getSubItem(item: Item) | async)?.title}}</span>
</div>

Вышеприведенное вызовет функцию, которая возвращает наблюдаемое, а ? означает, что результат является необязательным до того, как будет прочитано свойство title.

Вы можете видеть, что ограничение здесь. Если вам нужно более одного свойства, вы должны выполнить еще одну операцию async. Это просто не практично.

Вам нужно либо загрузить все данные для шаблона, либо изменить серверную часть API, чтобы получить все данные за один вызов API.

В конечном итоге вам понадобится что-то вроде этого.

 this.itemService.fetchItems().first().subscribe((items)=>{ 
       this.items = items;
       this.items.forEach((item)=>{ 
            this.getSubItem(item).first().subscribe((sub)=> {
                 // attach the child to the parent item
                 item.child = sub;
            });
       });
 });

Затем вы можете перебрать items в шаблоне и использовать item.child для подэлемента этого родителя. Вам нужно будет использовать оператор ?, так как данные загружаются лениво и не будут существовать при первом отображении элементов.

person Reactgular    schedule 28.05.2018
comment
Первый вариант на самом деле нежизнеспособен, поскольку он продолжает делать этот запрос (для подэлемента) на сервер из-за того, что я предполагаю, это механизм, с помощью которого Angular выполняет привязку (путем многократной оценки значения). - person Vlad Vidac; 28.05.2018
comment
Когда вы возвращаете объект в качестве значения, выражение рассматривается как изменяющееся, если каждый вызов создает новый объект. Хитрость заключается в том, чтобы каждый раз возвращать один и тот же объект для одних и тех же входных данных. - person Reactgular; 28.05.2018