20

I'm using Angular RxJs subscribe to make a HttpClient call and then make another call using the values from the first one. In this case, there's a call to get address object, and then i make a call using this object. Like this:

@Injectable()
export class AddressService {
  constructor(private http: HttpClient) { }

  getById(addressId: string, userId: string) {
    return this.http.get(BACKEND_URL + 'getAddressById/' + [addressId, userId]);
  }
}
  
export class AddressModalComponent implements OnInit {
  constructor(private alertService: AlertService, private addressService: AddressService,           @Inject(MAT_DIALOG_DATA) public data: any, private dropdownService: DropdownService)

  ngOnInit() {
    this.addressService.getById(this.data.id, this.data.userId)
        .subscribe(
          (address: Address) => {
            this.dropdownService.getCidadesBrByEstado(address.name)
              .subscribe((cities: BrCity[]) => {
                this.cities = cities;
                this.address = address;
              },
              error => console.log(error));
          }, error => { this.alertService.error(error);
          }
        );
    }
  }
}

I'm trying to avoid multiple Subscribes, there is many like this in my code. I need an Async/Await approach like Node.js promises, but using Observables at component level. I'm not very familiar with RxJs commands... is there a better way to make many calls with just one subscribe and catch?

James
  • 1,189
  • 2
  • 17
  • 32

3 Answers3

18

Try something like:

import { map, switchMap } from 'rxjs/operators'

this.addressService.getById(this.data.id, this.data.userId).pipe(
  switchMap(address => this.dropdownService.getCidadesBrByEstado(address.name).pipe(
    // this pass both cities and address to the next observable in this chain
    map(cities => ({ cities, address }))
  ))
).subscribe(({ cities, address }) => {
  this.cities = cities
  this.address = address
})
kctang
  • 10,894
  • 8
  • 44
  • 63
  • 1
    whats the difference between `switchMap`, `mergeMap` and `forkJoin`? im not sure if i can `switchMap` every subscription like this – James Sep 20 '18 at 00:36
  • 1
    Operators in RxJS is a very rich API. You have to google/read up on those and try them in simple examples to understand how they work. Not something I can explain in a few words. – kctang Sep 20 '18 at 00:55
  • 1
    For an explanation of the differences between `switchMap`, `mergeMap`, `concatMap` and `exhaustMap`, have a look at [this article](https://blog.angularindepth.com/switchmap-bugs-b6de69155524). – cartant Sep 20 '18 at 01:11
  • 3
    This answer looks just like callback hell. It is just more condensed than the code in the question. Imagine doing this with 4-5 sequential http requests. It would expand as much to the right as it does down with the switchMaps and pipes, and produce typical callback hell visuals. – Kristjan Liiva Feb 12 '21 at 20:49
  • Well, `switchMap`, `mergeMap` and `whateverMap` actually solve the problems with readabilty. I see. – Chris Pillen Sep 29 '21 at 13:24
  • @KristjanLiiva, they won't be _nested_ anymore! See [this](https://stackoverflow.com/a/53861335/4179032) answer for an example with three requests. – Leponzo Aug 24 '22 at 18:28
6

For angular when using RxJS, it suggests to use the Observable Class. To solve callback hell in the RxJS, You can use Observable' Operators api like switchMap() method(more methods for different scene are map(), concatMap(), ...). Here is my example about using the switchMap() method:
(1) Situation I met: I want to subsribe serviceC, but serviceC needs subsribe serviceB, and serviceB needs subsribe serviceA

const serviceA(params): Observable<any>;
const serviceB(params): Observable<any>;
const serviceC(params): Observable<any>;

serviceA(paramsA).subscribe(
    serviceAResult => {
        serviceB(paramsB).subscribe(
            serviceBResult => {
                serviceC(params).subscribe(
                    serviceCResult => {
                        // here is my logic code. Oh, Shit subscribe hell!
                    }
                )
            }
        )
    }
)

(2) Use switchMap() method to optimize code structure

const serviceB$ = serviceA(paramsA).pipe(
    switchMap(serviceAResult => {
        return serviceB(paramsB);
    })
);

const serviceC$ = serviceB$.pipe(
    switchMap(serviceBResult => {
        return serviceC(paramsC);
    })
);

serviceC$.subscribe(
    serviceCResult => {
        // here is my logic code.
    },
    error =>{
        // handle error
    }
);

Good post about dealing with callback hell.

Ron Strauss
  • 60
  • 1
  • 7
lei li
  • 147
  • 2
  • 7
  • 4
    While a link may be handy, it's good to include the essence of the solution here in case that link goes offline or moves. – craigcaulfield Dec 20 '18 at 01:52
2

Assuming, you don't actually care about streams you could also convert the Observables to promises in this case and use async/await:

async ngOnInit(): Promise<void> {
  this.address = await this.addressService.getById(this.data.id, this.data.userId).toPromise();
  this.cities = await this.dropdownService.getCidadesBrByEstado(this.address.name).toPromise();
}

And make sure you also catch the errors. try catch for example.

Kristjan Liiva
  • 9,069
  • 3
  • 25
  • 26