A more succinct way to conditionally chain multiple observables

Viewed 941

I have a method to update a 'Request' on a MySQL database. This request can optionally contain attachments, in which case I need to make additional HTTP requests to update or add the attachment records (to a different DB table) and upload the files.

I have a functional method to update the requests and optionally skip the attachments HTTP requests, but would like to know a more succinct way to achieve this. In general, I am new to Angular and RXJS, so the approach and use of rxjs operators is likely, not optimal.

Basically, I am looking for the best way to conditionally chain in some optional observables before subscription, or skip straight to the subscription.

I was looking at potential solutions with iif, returning Observable.empty() and different rxjs operators, but these seem to be options within the map functions when I just want to skip them altogether.

onUpdateRequest() {
    // if there are no attachments added to the request
    if (this.attachments.length <= 0) {
        this.callUpdateRequest().subscribe(() => {
            // some page and form tidy up
        });

    // there are attachments, so process the new request and then file uploads
    } else {
        this.callUpdateRequest().pipe(
            switchMap(() => {
                return of(this.attachments);
            }),
            mergeMap(attachments => {
                return attachments.map(attachment => {
                    return attachment;
                });
            }),
            mergeMap(attachment => {
              return this.attachmentsService.addAttachmentFile(attachment)
                  .pipe(map(fileData => {
                          return fileData;
                   }));
            }),
            mergeMap(fileData => {
                return this.attachmentsService.addAttachment(
                    this.requestId, fileData.fileUrl
                ).pipe(
                    map(attachments => {
                        return attachments;
                    })
                );
            }),
            takeLast(1)
        )
        .subscribe(() => {
            // some page and form tidy up
        });
    )
}

private callUpdateRequest() {
    return this.requestsService.updateRequest(
        // all the request params
    )
}
1 Answers

In RxJs there is a useful operator (also a static method) iif() which as it name says works in a similar way to JS if/else.

Used in way

iif(
  () => state to check,
  o1,  // Observable to execute when statement is truthy
  o2   // Observable to execute when statement is falsy
)

Also there is a defer() which you will need to create observable on demand (otherwise JS will try to compile an observable first and this may cause some errors, so I use it every time there is an incoming value used by observable).

Can be used like

iif(
  () => statmenet to check,
  defer(() => o1),
  defer(() => o2)
)

And your code may be rewritten in the following way

getRequest(attachments) {
    return iif(
        () => attachments.length === 0,
        defer(() => this.callUpdateRequest()),
        defer(() => this.callUpdateRequest().pipe(
            switchMapTo(of(attachments)),
            mergeMap(attachments => {
                return attachments.map(attachment => {
                    return attachment;
                });
            }),
            mergeMap(attachment => {
              return this.attachmentsService.addAttachmentFile(attachment)
                  .pipe(map(fileData => {
                          return fileData;
                   }));
            }),
            mergeMap(fileData => {
                return this.attachmentsService.addAttachment(
                    this.requestId, fileData.fileUrl
                ).pipe(
                    map(attachments => {
                        return attachments;
                    })
                );
            }),
            takeLast(1)
        ))
    );
}

onUpdateRequest() {
   this.getRequest(this.attachments).subscribe(() => {
       // do whatever you want
   })
}

I've split your code a bit to make it more general and testable.

P.S. Why write this.attachments.length <= 0 can it be lower that 0?

Related