Reputation: 837
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
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
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