1

I am trying to merge three observables and when the inner observable does not have any data, the mergeMap is not returning any data. I want to be able to continue the process even if one of the inner observable is empty. How do I handle this situation? Here is my code:

    ngOnInit() {
        this.accountStatusSub = this.accountService.accountNumberChange$.subscribe(
          accNumber => 
              {this.accountNumber = accNumber;


            var obs = this.accountService.getAccountDetails(this.accountNumber)
                  .pipe(mergeMap(accountData =>
                    this.accountService.getBill(accountData.account[0].accountNumber)
                      .pipe(mergeMap(billData =>
                        this.accountService.getPayment(accountData.account[0].accountNumber)
                          .pipe(map(paymentData => ({
                            address1: accountData.account[0].address1,
                            address2: accountData.account[0].address2,
                            city: accountData.account[0].city,
                            state: accountData.account[0].state,
                            zip: accountData.account[0].zip,
                            amountDue: billData.bill[0].amountDue,
                            dueDate: billData.bill[0].dueDate,
                            lastPaymentAmount: paymentData.payment[0].paymentAmount,
                            lastPaymentDate: paymentData.payment[0].paymentDate
                          })

                          ))
                      ))
                  ))


                obs.subscribe(combinedAccountData => {
                  console.log('MergeMap:', combinedAccountData)
                })

              })
          }

combinedAccountData is empty when either billData or paymentData is empty. Is there a better way to write the above code? I am new to angular and rxjs. Thanks.

satanTime
  • 12,631
  • 1
  • 25
  • 73
mywn9
  • 107
  • 12
  • Since you can know from the beginning whether the obs. will be empty or not, will there by a timeout which will _declare_ it as empty? – Andrei Gătej May 13 '20 at 09:49
  • Would you please elaborate on that? Where in the code can I do that? I am new to angular and still trying to figure out. – mywn9 May 13 '20 at 15:39
  • So you said that one observable can be empty, but how do you know it’s empty and does not take a bit longer than expected? Does it have to emit until a certain amount of time, otherwise it’s considered empty? Also, what do you mean by “continue the process” if one obs is empty? What’s the expected behavior ? – Andrei Gătej May 13 '20 at 16:11
  • The way it is working now is as per the above code, for example, if api call returns no rows for paymentData, there are no values set for accountData or billData. I want to be able to retrieve values for the rest of the fields (like address1, bill data etc) even if paymentData or billData (getBill or getPayment) does not return any rows. – mywn9 May 13 '20 at 18:22

1 Answers1

1

UPDATED

the desired bahevior was achieved by

ngOnInit() {
  this.accountStatusSub = this.accountService.accountNumberChange$.pipe(
    tap(accNumber => console.log(accNumber)),
    mergeMap(accNumber => {
      this.accountNumber = accNumber;
      const getAccount = this.accountService.getAccountDetails(accNumber);
      const getBill = this.accountService.getBill(accNumber);
      const getPayment = this.accountService.getPayment(accNumber);
      return forkJoin(getAccount, getBill, getPayment);
    })
   ).subscribe();
}

ORIGINAL

if they can return empty data you can return an empty object and spread it. Depends on structure you want to get.

ngOnInit() {
    this.accountStatusSub = this.accountService.accountNumberChange$.pipe(
        switchMap(accNumber =>
            this.accountService.getAccountDetails(accNumber),
        ),
        switchMap(accountData => combineLatest([
            of(accountData),
            this.accountService.getBill(accountData.account[0].accountNumber),
            this.accountService.getPayment(accountData.account[0].accountNumber),
        ])),
        map(([accountData, billData, paymentData]) => ({
            address1: accountData.account[0].address1,
            address2: accountData.account[0].address2,
            city: accountData.account[0].city,
            state: accountData.account[0].state,
            zip: accountData.account[0].zip,
            billData,
            ...( billData?.bill && billData.bill[0] ? {
              amountDue: billData.bill[0].amountDue,
              dueDate: billData.bill[0].dueDate,
            } : {}),
            paymentData,
            ...( paymentData?.payment && paymentData.payment[0] ? {
              lastPaymentAmount: paymentData.payment[0].paymentAmount,
              lastPaymentDate: paymentData.payment[0].paymentDate
            } : {}),
        }))).subscribe(combinedAccountData => {
            console.log('MergeMap:', combinedAccountData)
        });
}
satanTime
  • 12,631
  • 1
  • 25
  • 73
  • I think the OP refers to **empty observable** as an observable that emits empty data, not as one that does not emit at all. – Andrei Gătej May 13 '20 at 18:49
  • Really? Sounds too easy then. Simply you an if or ‚?.‘ – satanTime May 13 '20 at 19:32
  • Yes, Andrei Gătej is correct. I tried to use iif but wasn't quite sure how and where to use that. Please help. – mywn9 May 13 '20 at 19:54
  • try like that: `paymentData?.payment[0].paymentDate` - `?.` would it work? – satanTime May 13 '20 at 19:55
  • Done, but creds should go to Andrei – satanTime May 13 '20 at 19:57
  • why are you using both a ternary operator and optional chaining? shouldn't that be one or the other? – bryan60 May 13 '20 at 20:38
  • Thank you. I have just tried to implement your solution and these are the issues that I encountered: 1. Property 'length' does not exist on type 'Payment' 2. combinedAccountData doesn't have billData or paymentData data.( For eg: combinedAccountData.dueDate doesn't exist and more importantly, 3. when I change the accountNumber in a select list at the top of the page - the bill data and payment data are not changing even though the changed(right )account number is being passed to this.accountService.getBill call. I checked in my accountService class and I am seeing the right account number. – mywn9 May 14 '20 at 00:06
  • 1) I've updated the code, it looked like an array, if it's not then we should use `billData?.bill` and `paymentData?.payment`. 2) combinedAccountData doesn't have them, right (the same as in your example), I've added it. 3) we need to check what services emit, might you verify they return expected data when they get new account number? – satanTime May 14 '20 at 05:55
  • 1
    @satanTime - Thank you! The below code also seem to be working: ngOnInit() { this.accountStatusSub = this.accountService.accountNumberChange$.pipe( tap(accNumber => console.log(accNumber)), mergeMap(accNumber => { this.accountNumber = accNumber; const getAccount = this.accountService.getAccountDetails(accNumber); const getBill = this.accountService.getBill(accNumber); const getPayment = this.accountService.getPayment(accNumber); return forkJoin(getAccount, getBill, getPayment); }) ).subscribe(res => }) } – mywn9 May 14 '20 at 16:27