Как конвертировать вложенные подписки rxjs в карты и поддерживать логику вложенных подписок

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

*Использование rxjs 6.6.2

Родительский метод, вызывающий перезагрузку данных

  reloadData(partyId) {
    if (!!partyId) {
      this.pageData = new PartyPageData();
      this.reloadPartyDetails(partyId);
      this.getSupportingCurrencies();
    }
  }

Способ перезагрузки сведений о вечеринке

  reloadPartyDetails(partyId) {
    this.receiverService.getPartyDetails(partyId).pipe(
        take(1),
        map(response => response.body)
      ).subscribe(response => {
      this.pageData.party = response; 
      this.pageData.action = CONSTANTS.PARTY.ACTION.UPDATE;
      if (this.pageData?.party?.side?.toUpperCase() == CONSTANTS.PARTY.PARTY_SIDE.CREDITOR.toUpperCase()) {
        this.pageData.partySide = CONSTANTS.PARTY.PARTY_SIDE.RECEIVER;
        this.reloadBankDetails(partyId);
      }
      else {
        this.pageData.partySide = CONSTANTS.PARTY.PARTY_SIDE.SENDER;
      }
      this.changeDetector.detectChanges();
    }, error => {
      this.goBack();
    });
  }

Вложенный метод для перезагрузки банковских реквизитов

  reloadBankDetails(partyId) {
    this.receiverService.getAllPartyBankAccounts(partyId).pipe(
        take(1),
        map(response => response.body)
      ).subscribe(response => {
      if (!this.pageData?.bank) {
        this.pageData.bank = new BankDetails(CONSTANTS.PARTY.PARTY_SIDE.CREDITOR);
        this.pageData.bank.financialInstitution = new FinancialInstitution;
      }
      this.pageData.bank.accountName = response?.[0].accountName,
      this.pageData.bank.accountNumber = response?.[0].accountNumber;
      this.pageData.bank.currencyCode = response?.[0].currencyCode;
      this.pageData.bank.preferredFlag = response?.[0].preferred;
      this.pageData.bank.type = response?.[0].accountType;
      this.pageData.bank.memberId = response?.[0].financialInstitution.memberId;
      this.pageData.bank.financialInstitution = this.setFinanicalInstitution(response?.[0].financialInstitution);
      this.pageData.bank.accountId = response?.[0].accountId;
      this.pageData.bank.reEnterAccountNumber = response?.[0].accountNumber;

      this.changeDetector.detectChanges();
    }, error => {
      this.goBack();
    });
  }

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

Я посмотрел на rxjs concat() и concatMap(), но я не уверен, как их использовать, или как реорганизовать мой код, чтобы я мог их использовать.

Глядя на другие решения, которые рефакторируют карты, я не уверен, как свойства неявно присваиваются их целевым значениям. например как на связанной странице this.seller и this.rating назначаются.

Я попытался реорганизовать reloadPartyDetails, чтобы он больше походил на:

  reloadPartyDetails(partyId) {
    this.receiverService.getPartyDetails(partyId).pipe(
        take(1),
        map(response => response.body),
        mergeMap(response => { // or switchMap() or flatMap()?
      this.pageData.party = response; 
      this.pageData.action = CONSTANTS.PARTY.ACTION.UPDATE;
      if (this.pageData?.party?.side?.toUpperCase() == CONSTANTS.PARTY.PARTY_SIDE.CREDITOR.toUpperCase()) {
        this.pageData.partySide = CONSTANTS.PARTY.PARTY_SIDE.RECEIVER;
        return this.pageData.partySide;
      }
      else {
        this.pageData.partySide = CONSTANTS.PARTY.PARTY_SIDE.SENDER;
      }
      this.changeDetector.detectChanges();
    })).subscribe(partySide => {
      this.reloadBankDetails(partyId);
    });
  }

этот пример имел смысл, но я не понял, как его рефакторить, если .subscribe() не был пустым, как в моем случае.

Но я не уверен, как поддерживать внутреннюю функциональность reloadBankDetails(). Я также не уверен, является ли switchMap(), mergeMap(), flatMap() или concat() подходящим методом наблюдаемого отображения более высокого порядка для использования.

Существует только одна вложенная подписка, которая должна вызываться условно. Но переписывание таким образом, похоже, просто увековечивает вложенные подписки.

Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
0
0
56
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы попали в точку с concatMap, делая вид, что хотите сделать один вызов API, а затем другой (и что порядок имеет значение).

Первая попытка будет выглядеть примерно так

reloadPartyDetails(partyId) {
  this.receiverService.getPartyDetails(partyId).pipe(
    take(1),
    map(response => response.body),
    concatMap(response => {
      let shouldFetchBankDetails = false;
      this.pageData.party = response;
      this.pageData.action = CONSTANTS.PARTY.ACTION.UPDATE;
      if (this.pageData?.party?.side?.toUpperCase() == CONSTANTS.PARTY.PARTY_SIDE.CREDITOR.toUpperCase()) {
        this.pageData.partySide = CONSTANTS.PARTY.PARTY_SIDE.RECEIVER;

        shouldFetchBankDetails = true;

      }
      else {
        this.pageData.partySide = CONSTANTS.PARTY.PARTY_SIDE.SENDER;
      }
      this.changeDetector.detectChanges();

      if (shouldFetchBankDetails) {
        // concatMap expects you to return an observable
        return this.reloadBankDetails(partyId);
      } else {
        return of(); // from rxjs
      }
    }),
    catchError(error => {
      this.goBack();
    })
  ).subscribe(response => {

  }, error => {
    this.goBack();
  });
}

reloadBankDetails(partyId) {
  // we now return this api call, which allows us to subscribe once
  return this.receiverService.getAllPartyBankAccounts(partyId).pipe(
    take(1),
    map(response => response.body),
    tap(response => {
      if (!this.pageData?.bank) {
        this.pageData.bank = new BankDetails(CONSTANTS.PARTY.PARTY_SIDE.CREDITOR);
        this.pageData.bank.financialInstitution = new FinancialInstitution;
      }
      this.pageData.bank.accountName = response?.[0].accountName,
        this.pageData.bank.accountNumber = response?.[0].accountNumber;
      this.pageData.bank.currencyCode = response?.[0].currencyCode;
      this.pageData.bank.preferredFlag = response?.[0].preferred;
      this.pageData.bank.type = response?.[0].accountType;
      this.pageData.bank.memberId = response?.[0].financialInstitution.memberId;
      this.pageData.bank.financialInstitution = this.setFinanicalInstitution(response?.[0].financialInstitution);
      this.pageData.bank.accountId = response?.[0].accountId;
      this.pageData.bank.reEnterAccountNumber = response?.[0].accountNumber;

      this.changeDetector.detectChanges();
    }),
    catchError(error => {
      this.goBack();
    })
  );
  }
}

Высокий уровень, мы используем операторы для выполнения кода вместо того, чтобы делать все это внутри подписки. Разница между map и concatMap заключается в том, что concatMap ожидает, что вы вернете другой наблюдаемый объект — в этом случае подойдет любой вызов API. Оператор tap — это то, что мы используем, когда устанавливаем код, но не устанавливаем код для следующего наблюдаемого.

Теперь я заметил, что у вас там есть немного detectChanges. Это указывает на то, что у вас есть компонент, для которого установлено значение changeDetection: ChangeDetectionStrategy.OnPush, и когда приходят ваши результаты, компонент не обновляется. Если это не так, то вам, скорее всего, не нужен detectChanges.

Шаг 2 (или 3) рефакторинга заключается в том, чтобы иметь виртуальную машину в html, которая используется асинхронным каналом. Это позволяет html прослушивать, когда приходят новые значения, а затем использовать способ OnPush, сообщающий компоненту о перерисовке. Асинхронный канал также заменяет вашу подписку.

Что-то вроде

<div *ngIf = "vm$ | async as vm">
  ...
</div>
private reload$ = new Subject<number>();

vm$ = this.reload$.pipe(
  startWith(this.startingPartyId),
  concatMap((partyId)=>{
    this.receiverService.getPartyDetails(partyId).pipe(
      take(1),
      map(response => response.body),
      concatMap(response => {
        ... apply data to your local variable
        if (shouldFetchBankDetails) {
          return this.reloadBankDetails(partyId);
        } else {
          return of(); // from rxjs
        }
      }),
  })

Тогда ваши данные перезагрузки выглядят примерно так

reloadData(partyId) {
  if (!!partyId) {
    this.pageData = new PartyPageData();
    this.reload$(partyId);
    this.getSupportingCurrencies();
  }
}

Следующий рефакторинг, который я бы сделал, — заставить наблюдаемый объект возвращать переменную pageData (которую он создаст внутри concatMap), чтобы виртуальная машина (модель представления) содержала все состояние представления. Что-то вроде

vm$ = this.reload$.pipe(
  startWith(this.startingPartyId),
  concatMap((partyId)=>{
    // create page data
    const pageData = new PartyPageData();
    this.receiverService.getPartyDetails(partyId).pipe(
      take(1),
      map(response => response.body),
      concatMap(response => {
        ... apply data to your local variable
        if (shouldFetchBankDetails) {
          return this.reloadBankDetails(partyId, pageData).pipe(map(()=> pageData));
        } else {
          return of(pageData); // from rxjs
        }
      }),
  })

Надеюсь, эти примеры помогут — не стесняйтесь спрашивать об этом подробнее. Я ответил примерно так же здесь Кроме того, я все еще рекомендую что-нибудь от Деборы Кураты, она хорошо объясняет rxjs.

Вау, да, это очень помогает. Обычно я использую tap только для console.info и не заметил, что могу использовать его так. Спасибо.

D J 17.11.2022 15:44

Возможно, вам придется использовать of(null) вместо of() — я только что вспомнил, что у меня были проблемы с пустой функцией.

Wesley Trantham 17.11.2022 15:44

Другие вопросы по теме