Angular RXJS Sequential multiple post request

169 Views Asked by At

I tried to do following function with concat map. but i'm not sure what is the correct way for that.

//Req 01
await this.shippingInfoService.getShipmentInfoByCardId(this.data.CardId).then(async (shipmentData) => {
      if (shipmentData != undefined) {
        if (shipmentData.ShipmentId !== null && shipmentData.ShipmentId !== "") {
          //If shipment is assigned
          let shipmentId = shipmentData.ShipmentId;
          let contract = JSON.parse(sessionStorage.getItem(SITE_DETAILS_KEY)).Contract;

          let printObj = {
            "ShipmentId": Number(shipmentId),
            "Contract": contract
          }

//Second Req
          await this.orderDetailsService.printBillOfLading(printObj).then(async () => {
            await this.orderDetailsService.getBillOfLeadingPdfInfo(shipmentId).then(async (response) => {
              if (response.ResultKey === null || response.Id === null) {
                const dialogConfig = new MatDialogConfig();
                dialogConfig.data = "The information needed to generate a report isn't available.";
                dialogConfig.disableClose = true;
                this.dialog.open(ErrorDialogComponent, dialogConfig);
                //Hide Loading indicator
                this.store.dispatch(setLoadingSpinner({showLoading: false}));
              } else {
// 3rd Req
                let url = await this.orderDetailsService.generateBillOfLadingPdfUrl(response.ResultKey, response.Id);
                await window.open(url.url, "_blank");
                //Hide Loading indicator
                await this.store.dispatch(setLoadingSpinner({showLoading: false}));
              }
            });
          });
        } else {
          //If shipment is not assigned
          //Hide Loading indicator
          this.store.dispatch(setLoadingSpinner({showLoading: false}));
          //Error
          const dialogConfig = new MatDialogConfig();
          dialogConfig.data = "Shipment needs to be connected";
          dialogConfig.disableClose = true;
          this.dialog.open(ErrorDialogComponent, dialogConfig);
        }
      }
    })
  1. Request 02 is depend on Request 01
  2. Request 03 is depend on Request 02

Need to refactor above code with rxjs operators.

2

There are 2 best solutions below

0
On BEST ANSWER

This is a refactoring of your code, altering as little as possible (to a point). I don't think this is the most idiomatic way to write RxJS, but then I also wouldn't write Promises as above either.

I can't test any of this, so this is the shape of an answer, and not an answer unto itself. Maybe it'll get you started...

Best of luck:

//Req 01
from(this.shippingInfoService.getShipmentInfoByCardId(this.data.CardId)).pipe(
  filter(shipmentData => shipmentData != undefined),
  tap(shipmentData => {
    if (shipmentData.ShipmentId !== null && shipmentData.ShipmentId !== "") {
      throw "Shipment needs to be connected"
    }
  }),
  // Second Req because shipment is assigned
  concatMap(shipmentData => this.orderDetailsService.printBillOfLading({
      "ShipmentId": Number(shipmentData.ShipmentId),
      "Contract": JSON.parse(sessionStorage.getItem(SITE_DETAILS_KEY)).Contract
    }).pipe(
      map(_ => Number(shipmentData.ShipmentId))
    )
  ),
  concatMap(id => this.orderDetailsService.getBillOfLeadingPdfInfo(id)),
  tap(response => {
    if ( response.ResultKey === null || response.Id === null) {
      throw "The information needed to generate a report isn't available."
    }
  }),
  // 3rd Req
  concatMap(response =>
    this.orderDetailsService.generateBillOfLadingPdfUrl(response.ResultKey, response.Id)
  ),
  // Catch errors thrown above
  catchError(error => {
    // This isn't really comprehensive error management for obvious reasons. 
    // I'm assuming the only errors are one of the two strings thrown above.
    const dialogConfig = new MatDialogConfig();
    dialogConfig.data = error;
    dialogConfig.disableClose = true;
    this.dialog.open(ErrorDialogComponent, dialogConfig);

    // Returning empty means we don't propogate this error
    return EMPTY;
  }),
  // Always finish by Hiding Loading indicator
  finalize(() => this.store.dispatch(setLoadingSpinner({showLoading: false})))
).subscribe(
  url => window.open(url.url, "_blank")
);
0
On
  1. Not everything has to be an anonymous inline function, and 2) functions can return something. The general shape of the sequence looks more or less like that:
getShipmentInfo().pipe(
    map(response => parseShipmentData(response)),
    switchMap(printObj => forkJoin([
// Why do them in sequence? You aren't using results from `printBillOfLading` for anything
        printInvoice(printObj),
        getPdfInfo(printObj.ShipmentId),
    ]),
    switchMap(([nevermind, pdfResponse]) => handlePdfResponse(pdfResponse))
)

with parseShipmentData, printInvoice, getPdfInfo and handlePdfResponse filled with the relevant pieces of logic from your original code. BTW mixing await with then and nesting async functions is unnecessary and only obfuscates your code.