Дождитесь двух наблюдаемых (включая сбойную) перед рендерингом шаблона - PullRequest
2 голосов
/ 24 апреля 2020

У меня есть две наблюдаемые. Визуализация шаблона должна начинаться только тогда, когда ОБА Наблюдаемые завершены или не выполнены:

  • Наблюдаемая 1 завершена и Наблюдаемая 2 завершена или
  • Наблюдаемая 1 завершена, но Сбой Observable 2
  • При сбое Observable 1 Observable 2 не важен, потому что шаблон не будет полностью обработан, тогда

(Игнорировать тип <any>, здесь только для упрощения )

Компонент:

@Component({
  selector: 'app-page',
  templateUrl: './page.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class PageComponent implements OnInit {
  obs1$ = new Subject<any>();
  obs2$ = new Subject<any>();

  isLoading = true;
  isObs1Error: boolean;
  isObs2Error: boolean;


  ngOnInit() {
    this.initializeDataRetrieval();
  }

  initializeDataRetrieval() {
    this.obs1$ = this.obs1Method();
    this.obs1$.subscribe((response: any) => {
      this.isObs1Error = false;
      this.obs1 = response;

      this.obs2$ = this.obs2Method();
      this.obs2$.subscribe((response: any) => {
        this.isObs2Error = false;
        this.isLoading = false;
        this.obs2 = response;
        this.cdr.detectChanges();
      });
    });
  }

  private obs1Method(): any {
    return this.obs1Service
      .getStuff()
      .pipe(
        catchError(() => {
          this.isError = true;
          this.isLoading = false;
          this.cdr.detectChanges();
          return EMPTY;
        })
      );
  }

  private obs2Method(): any {
    return this.obs2Service
      .getStuff()
      .pipe(
        catchError(() => {
          this.isObs2Error = true;
          this.isLoading = false;
          this.cdr.detectChanges();
          return EMPTY;
        })
      );
  }

  canDisplayContent(): boolean {
    return !this.isLoading && !this.isObs1Error;
  }

Шаблон:

<ng-container *ngIf="isLoading">
  <app-loading-indicator></app-loading-indicator>
</ng-container>

<ng-container *ngIf="isObs1Error">
  <div class="error">
    This Obs1 stuff could not be loaded currently
  </div>
</ng-container>

<ng-container *ngIf="canDisplayContent()">
  <div class="error" *ngIf="isObs2Error">
    Technical error
  </div>
  More content here which is shown when at least Obs1 doesn't had an error
</div>

Так что в основном:

  • Я хочу подождать с рендерингом шаблона, пока оба Observables готово и покажите индикатор загрузки в течение времени
  • Когда есть ошибка с Obs1, тогда покажите сообщение
  • Когда есть ошибка с Obs2, тогда выполните рендеринг 3-го ng-container с Obs2 сообщение об ошибке

Я уверен, что код TS можно упростить, используя ... какой оператор Rx JS? Хотя чтение через Rx JS Операторы для чайников: forkJoin, zip, combLatest, withLatestFrom Я не уверен, подходит ли что-либо из этого. Насколько я понял, например, combineLatest успешен только тогда, когда оба потока успешно завершены ...

Любая подсказка приветствуется, спасибо.

Ответы [ 2 ]

2 голосов
/ 24 апреля 2020

Я хотел бы использовать forkJoin для этого случая.

Код будет выглядеть следующим образом

forkJoin(this.obs1Method(), this.obs2Method()).subscribe(
   ({resp1, resp2}) => {
        this.isLoading = false;
        this.obs2 = resp1;
        this.obs2 = resp2;
        this.cdr.detectChanges()
   }
)

Возможно, вам также придется немного изменить добавление obsxMethod s tap для установки свойств ошибки на false в случае успешного извлечения данных и удаления настроек, которые выполняются в пределах subscribe, например

private obs1Method(): any {
    return this.obs1Service
      .getStuff()
      .pipe(
        tap(() => this.isError = false),
        catchError(() => {
          this.isError = true;
          return EMPTY;
        })
      );
  }

  private obs2Method(): any {
    return this.obs2Service
      .getStuff()
      .pipe(
        tap(() => this.isObs2Error = false),
        catchError(() => {
          this.isObs2Error = true;
          return EMPTY;
        })
      );
  }
0 голосов
/ 24 апреля 2020

Вот как я это сделаю:

const decorateObservable = (obs$, key) => obs$.pipe(
  mapTo(false), // `false` -> no error
  catchError(() => of(true)), // `true` -> error found
  map(value => ({ key, value })) // Identification
)

const base$ = merge(
  decorateObservable(obs1$, 'obs1'),
  decorateObservable(obs2$, 'obs2'),
).pipe(
  // When the source is unsubscribed(`error`/`complete`),
  finalize(() => this.isLoading = false),
  share(),
)

const obs1Displayed$ = base$.pipe(
  filter(o => o.key === 'obs1'),
  map(o => o.value),
)

const obs2Displayed$ = base$.pipe(
  filter(o => o.key === 'obs2'),
  map(o => o.value),
)

Оператор share() используется здесь, поскольку нежелательна многократная подписка на источник.

В этом случае вы подписывался бы дважды (в шаблоне), потому что обе отображаемые наблюдаемые происходят из одной и той же базы. Что share делает для многоадресной передачи производителя данных.

share совпадает с pipe(multicast(() => new Subject()), refCount()). refCount указывает, что производитель будет вызван , как только появится первый подписчик.

Иными словами, потребитель данных решает, когда производитель должен начать свою логику c .

Обратите внимание, что я предположил, что obs1$ и obs2$ генерируют значения асинхронно.

По сути, это почти то же самое, что и:

const s  = new Subject();

// The subscriptions happen inside the template
s.pipe(filter(o => o.key === 'obs1', ...).subscribe(observer1)
s.pipe(filter(o => o.key === 'obs2', ...).subscribe(observer2)

// And later on...
s.next({ key: 'obs1', value: false }) // `obs1$` emitted

А вот и шаблон:

<ng-container *ngIf="isLoading">
  <app-loading-indicator></app-loading-indicator>
</ng-container>

<ng-container *ngIf="obs1Displayed$ | async">
  <div class="error">
    This Obs1 stuff could not be loaded currently
  </div>
</ng-container>

<ng-container *ngIf="obs2Displayed$ | async">
  <div class="error">
    Technical error
  </div>
  More content here which is shown when at least Obs1 doesn't had an error
  </div>
...