Challenging Scenario: Whenever a source Observable emits an event, it triggers a sequence of API calls and Angular services. Some of these invocations rely on previous results.
For instance, the source Observable startUpload$
initiates a series of interdependent calls.
This can be achieved using destructuring as shown below:
this.startUploadEvent$.pipe(
concatMap(event => this.getAuthenticationHeaders(event)),
map(({ event, headers }) => this.generateUploadId(event, headers)),
tap(({ event, headers, id }) => this.emitUploadStartEvent(id, event)),
concatMap(({ event, headers, id }) => this.createPdfDocument(event, headers, id)),
concatMap(({ event, headers, id, pdfId }) => this.uploadBilderForPdf(event, pdfId, headers, id)),
mergeMap(({ event, headers, id, pdfId, cloudId }) => this.closePdf(cloudId, event, headers, id, pdfId)),
tap(({ event, headers, id, pdfId, cloudId }) => this.emitUploadDoneEvent(id, event, cloudId)),
).subscribe()
While this approach resembles imperative programming, it comes with its own set of challenges:
- The long and repetitive destructuring chain
throughout the code{ event, headers, id, pdfId, cloudId }
- Methods like
generateUploadId(event, headers)
need to receive all previous values, even if they don't require them, just to pass them along to the next pipe stage - Inner Observables within methods are needed to map the values for further stages of the pipe:
_
private closePdf(cloudId, event, headers, id, pdfId) {
return this.httpClient.post(..., { headers } )
.pipe(
//...,
map(() => ({ event, headers, id, pdfId, cloudId }))
)
}
Wouldn't it be ideal if the compiler could handle this boilerplate, similar to how async await
simplifies asynchronous code? The desired code structure would eliminate the mentioned issues and look something like this:
private startUpload(event: StartUploadEvent) {
const headers = this.getAuthenticationHeaders(event)
const id = this.generateUploadId()
this.emitUploadStartEvent(id, event)
const pdfId = this.createPdfDocument(event, headers, id)
this.uploadBilderForPdf(event, pdfId, headers, id)
const cloudId = this.closePdf(headers, pdfId)
this.emitUploadDoneEvent(id, event, cloudId)
return cloudId
}
Is there a way to seamlessly pass results between chained observables without encountering the previously discussed challenges? Are there any concepts in rxjs that address this issue?