Sivakanesh
Sivakanesh

Reputation: 837

Waiting for nested Observables from Store correctly in Angular 2

I'm trying to create a method with nested dependent observables, where the second one waits for the first one. And then returns a bool.

What the code is doing is: gets list of assets from the store, gets the root asset, and then get the status from the database and then updates the UI.

But what happens is that the value of false is returned from the outer subscription first, and then the UI is updated correctly by the inner subscription like it's supposed to. But it should return a value of true when it completes rather than returning a false before it completes.

I understand why this is, and I need to make the outer one wait. After looking at many other questions similar to this here, I have tried using switchMap and resultSelector. But I'm completely lost on how to use them correctly in this scenario, with tap.

Any advice would be appreciated.

getAndUpdateHexStatus (startTime, endTime): Observable<boolean> {
  this.store.pipe(
      select((assets) => assets.dashboard.assets), //get the assets
      tap((assets) => {
        var rootAsset = assets.filter((value) => this.isRootAsset(value)); //get the root asset
        if (rootAsset) {
          this.getDetailDataForAssetAndDates(rootAsset[0].assetId, new Date(startTime), new Date(endTime)) //get status data from the DB.
            .subscribe((data) => {
              this.updateAssetStatus(data); //Update the UI
              return of(true);
            });
        }

      }),
      takeUntil(this.componentDestroyed$)
    )
    .subscribe();
    return of(false);
}

Upvotes: 0

Views: 670

Answers (2)

Barremian
Barremian

Reputation: 31125

You are close. You're returning the of(false) immediately on the execution of the function, so it's returned first.

Instead you need to use a higher order mapping operator like switchMap and within it's body decide what to return. If the condition is true, then return the getDetailDataForAssetAndDates() with a map operator piped in to update the UI and return true. Or if the condition if false, use of() to return false because switchMap needs to return an observable.

getAndUpdateHexStatus(startTime, endTime): Observable<boolean> {
  return this.store.pipe(
    select((assets) => assets.dashboard.assets), //get the assets
    switchMap((assets: any) => {
      rootAsset = assets.filter((value) => this.isRootAsset(value));
      return (!!rootAsset)
        ? this.getDetailDataForAssetAndDates(rootAsset[0].assetId, new Date(startTime), new Date(endTime)).pipe(
            map(data => {
              this.updateAssetStatus(data);
              return true;
            })
          )
        : of(false)
    })
  );
}

Upvotes: 1

Mrk Sef
Mrk Sef

Reputation: 8022

Here is (roughly) how I'd implement this:

Two things to note, your function is returning an observable. This means it's rarely the case that you want to subscribe anywhere within that function. Your update asset status is handled as a side-effect, so I've left it in a tap operator.

Generally, nested subscriptions can be handled via mergeMap, though it's often preferable to use switchMap or concatMap instead to save networking or guarantee order. I think in cases like this, that's a secondary concern.

function getAndUpdateHexStatus (startTime, endTime): Observable<boolean> {
  return this.store.pipe(
    // get the assets
    select((assets) => assets.dashboard.assets), 

    // merge observable that emits true/false
    mergeMap(assets => {
      // get the root asset
      const rootAsset = assets.filter(v => this.isRootAsset(v));
      return rootAsset?.length > 1 ?
        // get status data from the DB.
        this.getDetailDataForAssetAndDates( 
          rootAsset[0].assetId, 
          new Date(startTime), 
          new Date(endTime)
        ).pipe(
          // Update UI
          tap(data => this.updateAssetStatus(data)),
          // UI updated, emit true
          mapTo(true)
        ) : 
        // No root asset, just emit false
        of(false)
    }),

    take(1)
  );
}

Upvotes: 1

Related Questions