RxJs / угловая загрузка из нескольких HTTP-запросов и обновление модели из каждого запроса - PullRequest
0 голосов
/ 03 февраля 2019

Я использую Angular / RxJS в течение нескольких недель и имею различные модели, построенные из нескольких запросов REST, которые я до сих пор достигал с помощью switchMap ().Вот простой надуманный пример (stackblitz: https://stackblitz.com/edit/angular-o1djbb):

import { Component, OnInit, } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay, switchMap } from 'rxjs/operators';

interface Order {
  id: string;
  itemName: string;
  details?: string;
}

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  order: Order;

  ngOnInit() {
    this.getOrderFromApi(123)
      .subscribe(item => this.order = item);
  }

  getOrderFromApi(id): Observable<Order>  {
    const item$ = this.getItemName(id);
    const fullItem$ = item$.pipe(
      switchMap(n => {
        console.log(`Got name: '${n}''`);
        return this.getDetails(n);
      },
        (nameReq, detailsReq) => ({ id: '123', itemName: nameReq, details: detailsReq })
      ));
    return fullItem$;
  }

  getItemName(id): Observable<string> {
    return this.fakeXhr('foo');
  }

  getDetails(itemName): Observable<string> {
    console.log(`Got details '${itemName}''`)
    return this.fakeXhr('Some details about foo');
  }

  fakeXhr(payload: any) {
    return of(payload)
      .pipe(delay(2000));
  }
}

и простой шаблон:

<p>
  item: {{order && order.itemName}}
</p>
<p>
  details: {{order && order.details}}
</p>

Это работает, но информация о заказе не отображается, пока оба *Выполнено 1009 * запросов. Я хотел бы, чтобы itemName отображался как только он становился доступным, а затем детали отображались, когда они становились доступными, и, следовательно, использовали многочисленные значения, которые могут генерировать Observables.Например:

// first value emitted:
{ itemName: 'foo', details: null }
// second value emitted:
{ itemName: 'foo', details: 'Some details about foo' }

Я понимаю, что, вероятно, смог бы достичь этого с помощью BehaviourSubject или с помощью Redux (как я это делал в прошлом с React), но чувствую, что есть простое решение, которое я не совсем понимаю из-зановизна всего этого для меня.

Ответы [ 4 ]

0 голосов
/ 04 февраля 2019

Используйте expand, чтобы сразу отправить данные из первого запроса, а затем выполнить второй запрос.

expand будет рекурсивно вызывать внутренний запрос, получая order из последнего запроса в качестве входных данных, поэтому мы выполняем второй запрос только тогда, когда order не имеет details, и в противном случае завершаем рекурсиюс EMPTY Observable.

import { Observable, EMPTY } from 'rxjs';
import { map, expand } from 'rxjs/operators';

getOrderFromApi(id): Observable<Order> {
  return this.getItemName(id).pipe(
    map(itemName => ({ id, itemName, details: null } as Order)),
    expand(order => order.details
      ? EMPTY
      : this.getDetails(order.itemName).pipe(
        map(details => ({ id, itemName: order.itemName, details } as Order))
      )
    )
  );
}

https://stackblitz.com/edit/angular-umqyem

Возвращенное Observable будет выдавать:

  1. { "id": 123, "itemName": "foo", "details": null }
  2. { "id": 123, "itemName": "foo", "details": "Some details about foo" }
0 голосов
/ 04 февраля 2019

Вместо сохранения вашего полного элемента в конце наблюдаемого потока (т. Е. В вашем блоке .subscribe) вы можете хранить полученные значения в соответствующих частях вашего кода, а именно после того, как они станут доступны:

 switchMap(n => {
        console.log(`Got name: '${n}''`);
        this.order.name = n;
        return this.getDetails(n);
      },

... и тогда в вашем блоке sibscription вам нужно будет только сохранить расширенную информацию:

this.getOrderFromApi(123)
      .subscribe(item => this.order.details = item.details);

Очевидно, что для этого требуется, чтобы вы соответственно инициализировали свой атрибут 'order', например, с помощью order: Order = {}; или order: Order = new Order();.

По сути, идея состоит в том, чтобы реструктурировать ваш код, чтобы ваш наблюдаемый поток выдавал частичные значения (атрибуты вашего заказа) вместо полноценного объекта.

0 голосов
/ 04 февраля 2019

Одним из решений, которое я придумал, было просто создать Observable и вызвать next ().Мне было бы интересно услышать любое мнение по этому поводу.

getOrder(id) {
    const ob$ = new Observable(subscriber => {
      const item$ = this.getItem(id)
        .pipe(
          tap(o => subscriber.next(({ id: id, itemName: o.itemName, details: null }))),
          switchMap(o => this.getDetails(o.itemName),
            (o, d) => ({ id: id, itemName: o.itemName, details: d.details })
          ),
          tap(a => subscriber.next(a))
        );
      item$.subscribe(a => {
        subscriber.complete();
      });
    });

    return ob$;
  }

Stackblitz: https://stackblitz.com/edit/angular-mybvli

0 голосов
/ 03 февраля 2019

Может быть, ваш лучший выбор - заменить mergeMap() на switchMap()?

https://blog.angular -university.io / rxjs-высшего порядка-отображения /

Как мы видим, значения объединенных исходных наблюдаемых сразу появляются в выходных данных.Результат Observable не будет завершен, пока все объединенные Observables не будут завершены.

Я бы определенно проверил следующие статьи:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...