[Fixed] Why does RxJs finalize execute before chained http calls are complete?

Issue

Angular 11 site. I’ve searched all over but I’m still struggling with what seems like it should be a pretty common scenario.

Goal: Do an http call to retrieve a list of items, then do another http call to retrieve details for the first item. After both calls are done (whether they succeeded or failed), hide the component’s overall wait indicator.

Here is a highly simplified version without null-checking and error handling:

  ngOnInit(): void {
    this.getPackages();
  }

  private getPackages() {
    this._packageSubscription = this.packageService.getPackages()
      .pipe(
        finalize(() => {
          console.log('getPackages finalize');
          this.loadingPackages = false; //hide wait indicator: happens BEFORE getPackageDetails is finished
        })
      )
      .subscribe(
        packages => {
          this.packageOptions = packages;
          this.selectedPackage = packages[0];
          this.loadSelectedPackage();
        }
      );
  }

  public loadSelectedPackage() {
    this._packageDetailsSubscription = this.packageService.getPackageDetails(this.selectedPackage!.Id)
      .pipe(
        finalize(() => {
          console.log('getPackageDetails finalize');
        })
      )
      .subscribe(
        packageDetails => {
          this.packageDetails = packageDetails;
        }
      );
  }

When executed, the console reads:

getPackages finalize
getPackageDetails finalize

I would expect it the other way around.

I’m aware I could add my code to deactivate the wait indicator in the inner finalize() for getPackageDetails(), but there are a few issues with that:

  1. It just seems wrong. Why can’t I know when the chain is complete at the outer level?
  2. That would only cover the case where everything succeeded. I’d have to add a second line of code somewhere to hide the wait indicator when something with the first call failed.
  3. loadSelectedPackage() can be called again later after a user action changes the selected package, so it makes even less sense for it to deal with something that is only relevant on the initial load.

So why doesn’t my outer finalize() wait until the chain is complete? Or how else can I know when the chain is complete?

Solution

It’s actually the correct result you are getting. The way you wrote you code there is no chain. Those two subscriptions are independent of each other, by calling this.loadSelectedPackage(); from within the subscribe you are not concatenating them or anything like that.

You should actually use some map operator and subscribe only one. In your case I believe concatMap() is suitable. Let the getPackages() happen, assign the values to object properties and then concatenate new observable to get details.

private getPackages() {
  this._packageSubscription = this.packageService.getPackages()
    .pipe(
      concatMap(packages => {
        this.packageOptions = packages;
        this.selectedPackage = packages[0];
        return this.packageService.getDetails(this.selectedPackage!.Id);
      }),
      finalize(() => {
        console.log('getPackages finalize');
        this.loadingPackages = false; 
      })
    )
    .subscribe(packageDetails => {
      this.packageDetails = packageDetails;
    });
}

This should do the trick.

Leave a Reply

(*) Required, Your email will not be published