1

I had a nested Observable situation, and after some research I found that it's best to use RxJS switchMap.

For my case the first observable contains a Promise, so I have to await the first subscription result before it's passed to the second observable. Now the second Observable is not being subscribed to and is returning an Observable instead of subscribing to its results.

firstObservable(x) {
  return this.productService.getProduct(x).pipe(
    map(async result => {
       this.productObject = {...result};
       let timeVariable = await 
       this.distanceMatrixService.getTime(this.productObject, this.currentLocation);
       return this.productObject;
    })
}

async secondObservable(passedProductObject) {    
  const productObject = await passedProductObject;
  return this.productService.getProductsByShop(productObject.store_id).pipe(
    map(result => {
      ////STUFF that depends on the timeVariable 
    }));
} 

chainingObservables(x) {
  return this.firstObservable(x).pipe(
  switchMap(async x => this.secondObservable(x)));
}

this.chainingObservables(this.passedData).subscribe(res => {
 console.log("result", res);
})

result Observable {_isScalar: false, source: Observable, operator: MapOperator}

Any suggestion would be appreciated.

Dmitry S.
  • 1,544
  • 2
  • 13
  • 22
Berkenus
  • 23
  • 4
  • You don't need any async/await. Try removing them and tell us if that works ? – Random Apr 06 '21 at 08:26
  • 1
    `switchMap` works natively with Promises. In your `firstObservable` function, use `switchMap` instead of `map` and your code should work. – pascalpuetz Apr 06 '21 at 08:29
  • Solution using forkJoin: First, get rid of firstObservable and secondObservable and put all of this in one single method (see if it works and then try to refactor to makae it prettier) const observables: Observable[] = []; // TODO: Change any to the known type observables.push(this.productService.getProduct(this.passedData)); observables.push(this.productService.getProductsByShop(this.passedData) forkJoin(observables) .pipe( map(data => { // do your mapping stuff here }) ) – Pedro Bezanilla Apr 06 '21 at 08:34
  • Hi, I actually need to await the result from the distanceMatrixService.getTime as it returns a promise. and to execute it I need to supply the result from the first subscription. – Berkenus Apr 06 '21 at 08:34
  • @Berkenus That's how switchMap works, it already handles that. No need for async/await. – Random Apr 06 '21 at 08:35
  • @Berkanus if you need to do something only when both observables emits then use combineLatest – Pedro Bezanilla Apr 06 '21 at 08:35
  • Got it, thanks a lot, I'll give it a try – Berkenus Apr 06 '21 at 08:37
  • @Random It worked perfectly, thanks. But instead I took your first advice literally and removed the Promise from the first observable and put it in the subscription block. So i'm using the distanceMatrixService.getTime after I get the results of both observables. – Berkenus Apr 06 '21 at 12:31
  • Looks good indeed, since you don't use the time in the first observable :) – Random Apr 06 '21 at 14:40

2 Answers2

1

You could do this in a pipe chain using switchMap operator like this.

you should not use the async with observables

assuming this.distanceMatrixService.getTime returns a valid promise.

    observable(x) {
        return this.productService.getProduct(x).pipe(
            tap(result => {
                this.productObject = {...result};
            }),
            switchMap(() => {
                return from(this.distanceMatrixService.getTime(this.productObject, this.currentLocation))
                    .pipe(map((time) => [time, this.productObject]));
            }),
            switchMap(([time, passedProductObject]) => {
                return this.productService.getProductsByShop(passedProductObject.store_id).pipe(
                    map(result => {
                        ////STUFF that depends on the timeVariable
                        return time;
                    }));
            }));
    }

    subMethod(x) {
        this.observable(this.passedData).subscribe(res => {
            console.log('result', res);
        });
    }
waseemrakab
  • 315
  • 2
  • 9
  • 1
    Thanks for your answer, this was very helpful for my issue, but also for all my project, it taught me a great practice of how to structure my observables use in a clean way, especially the tap operator! thanks a lot – Berkenus Apr 11 '21 at 18:35
0

You could use RxJS from function to convert the promise to an observable. After that a couple of switchMaps with map ought to suffice your requirement.

Try the following

this.productService.getProduct(x).pipe(
  map(result => ({...result})),
  switchMap(productObject => 
    from(this.distanceMatrixService.getTime(productObject, this.currentLocation)).pipe(
      map(timeVariable => ({
        productObject: productObject,
        timeVariable: timeVariable
      }))
    )
  ),
  switchMap(({productObject, timeVariable}) =>
    this.productService.getProductsByShop(productObject.store_id).pipe(
      map(result => {
        // stuff that depends on `timeVariable`
      })
    )
  )
).subscribe(
  res => {
    console.log("result", res);
  },
  error => {
    // handle error
  }
);
ruth
  • 29,535
  • 4
  • 30
  • 57