Angular: как синхронизировать двух наблюдателей в **разных местах** кода

avatar
Gabriel
1 июля 2021 в 19:58
160
4
3

Фон

В компоненте Angular мне нужно загрузить некоторые настройки перед загрузкой данных, поэтому у меня есть следующий код:

  constructor(
    private dataService: DataService,
    settingsService: SettingsService
  ) {
    settingsService.get().subscribe({
      next: (settings) => this.settings = settings
    });
  }

  onInit() {
    this.loadData();
  }

  loadData() {
    this.dataService.get(this.settings).subscribe({
      next: (data) => this.data = data
    });
  }

Как видите, this.settings должен быть загружен перед для вызова loadData(), который вызывается при инициализации и впоследствии, когда пользователь нажимает кнопку. В то время как settingsService.get() вызывается только один раз в начале, this.dataService.get() может вызываться много раз.

Проблема

Первый вызов loadData() от onInit() был вызван до завершения settingsService.get(), поэтому параметр не определен, и я получаю сообщение об ошибке.

Вопрос

Как мне синхронизировать их для этой конкретной ситуации?

ПРИМЕЧАНИЕ

Есть аналогичный вопрос в Angular — сделать несколько HTTP-вызовов последовательно, но я считаю, что это другой сценарий, потому что в этом случае оба наблюдателя вызываются каждый раз вместе, последовательно. В данном конкретном случае мне нужно синхронизировать их только в первый раз, поэтому я не понимаю, как здесь можно использовать switchMap. Я не эксперт RXJS, поэтому, если я не прав, буду рад совету. Спасибо.

Источник

Ответы (4)

avatar
Timothy
2 июля 2021 в 13:09
0

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

constructor(
    private dataService: DataService,
    settingsService: SettingsService
) {}

onInit() {
    this.loadData();
}

loadData() {
    this.settingsService.get().pipe(
        switchMap(settings => this.dataService.get(settings))
    ).subscribe(data => {
        // do something
    })
}
avatar
Iacopo Ciao
2 июля 2021 в 13:02
-1

вы можете использовать repeatWhen:

    private reload$ = new Subject<void>();
    
    constructor(
        private dataService: DataService,
        settingsService: SettingsService
    ) {}
 
    onInit() {
        settingsService.get()
            .pipe(
                switchMap(settings => this.dataService.get(settings).pipe(repeatWhen(() => reload$))),
                
            ).subscribe({next: (data) => this.data = data});
    }

    reloadData() {
        this.reload$.next();
    }
avatar
carlokid
2 июля 2021 в 04:03
0

Думаю, здесь вам нужна Тема. См. код и пояснения ниже.

  //will be use to trigger if settings api completes
  private settingsReady: Subject<any> = new Subject<any>();
  
  //make settingsReady as observable so we can subscribe to it
  private settingsReady$: any = this.settingsReady.asObservable();
  
  //mock the settings api that will be executed after 5 sec
  private settingsApi$ = of('settingsApi').pipe(
    delay(5000),
  );
  
  //mock the data api that will be executed after 2 sec
  private dataApi$ = of('dataApi').pipe(
    delay(2000),
  );
  
  constructor() { 
    //trigger the settings api
    this.settingsApi$.subscribe((result: any) => {
      console.log('settingsApi execute after 5 seconds =>', result);
  
      //inform settingsReady subject that setting api already complete
      //##KEY_PART you can also pass the result of the api here
      this.settingsReady.next(result);
    });
  }
  
  ngOnInit() {
    //will only trigger once settings api complete
    //that is when settingsReady call next on ##KEY_PART
    this.settingsReady$.subscribe((settingsResult) => {
      console.log('settings result from Subject =>', settingsResult);
      this.loadData();
    });
  }
  
  loadData() {
    //trigger the data api
    this.dataApi$.subscribe((result) => {
      console.log('dataApi execute after settingsApi completes =>', result);
    });
  }

Результат приведенного выше кода:

> settingsApi execute after 5 seconds => settingsApi
> settings result from Subject => settingsApi
> dataApi execute after settingsApi completes => dataApi

Примечание. В некоторых случаях вам может потребоваться немедленно запустить loadData, не дожидаясь первоначального возврата API настроек. Для такого сценария вы можете изучить BehaviorSubject.

avatar
Daniel Gimenez
1 июля 2021 в 20:08
1

Вы можете просто использовать switchMap, чтобы сначала загрузить данные из настроек, а затем вызвать dataService.get() с результатом:

readonly data$ = this.settingsService.get().pipe(
  switchMap(settings => this.dataService.get(settings))
);

constructor(private dataService: DataService, private settingsService: SettingsService) {}

Обратите внимание, что я не буду использовать хук жизненного цикла ngOnInit(), а просто создам поток, который будет потребляться с помощью асинхронного канала в шаблоне. Это делает код намного меньше, и вам не нужно отписываться от подписок в хуке ngOnDestroy().

.
  • Если вам нужен только один результат, и любой из методов остается открытым, вы можете легко добавить take(1) после switchMap.
  • Если существует несколько привязок к data$ в шаблоне then, рассмотрите возможность добавления оператора shareReplay в конец потока, чтобы не выполнять несколько базовых вызовов.
Gabriel
1 июля 2021 в 21:29
0

Спасибо @Daniel Gimenez, но я не вижу в этом примере, как сделать последующие вызовы this.dataService.get() без вызова this.settingsService.get() вместе через переменную data$

DeborahK
1 июля 2021 в 22:32
0

При каких обстоятельствах вам нужно снова позвонить по this.dataService.get()? Настройки меняются?

Gabriel
2 июля 2021 в 11:27
0

Привет, @DeborahK, этот код является минимальным воспроизводимым примером. На самом деле this.dataService.get() содержит больше параметров, которые зависят от взаимодействия с пользователем. Так что да, настройки меняются.

DeborahK
2 июля 2021 в 18:05
0

Вы можете определить потоки «действия». Вот статья: tomastrajan.medium.com/… У меня есть пример на github (с использованием нумерации страниц вместо настроек, но идея та же): github.com/DeborahK/Angular- ActionStreams Вот выступление на эту тему: youtube.com/watch?v=OKZBHuYa-wc