James
James

Reputation: 1219

How to avoid RxJs subscribe callback hell?

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?

Upvotes: 21

Views: 8727

Answers (3)

lei li
lei li

Reputation: 167

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.

Upvotes: 6

Kristjan Liiva
Kristjan Liiva

Reputation: 9569

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.

Upvotes: 2

kctang
kctang

Reputation: 11202

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
})

Upvotes: 18

Related Questions