Проблема: я пытаюсь реализовать логику для перезагрузки данных страницы. При вызове 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() подходящим методом наблюдаемого отображения более высокого порядка для использования.
Существует только одна вложенная подписка, которая должна вызываться условно. Но переписывание таким образом, похоже, просто увековечивает вложенные подписки.
Вы попали в точку с 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.
Возможно, вам придется использовать of(null)
вместо of()
— я только что вспомнил, что у меня были проблемы с пустой функцией.
Вау, да, это очень помогает. Обычно я использую tap только для console.info и не заметил, что могу использовать его так. Спасибо.