Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 666
James Avatar asked Sep 19 '18 23:09

James


People also ask

Why you should not put any logic in the RxJS subscribe callback in angular?

It triggers the call and handles the result. Coming back to the main topic of the article, if you are writing a lot of logic inside the subscribe callback, you are using an Observable as if it was a Promise. Thus, you are not making an optimal use of RxJS or Angular features.


3 Answers

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
})
like image 103
kctang Avatar answered Oct 18 '22 00:10

kctang


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.

like image 5
lei li Avatar answered Oct 18 '22 00:10

lei li


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.

like image 2
Kristjan Liiva Avatar answered Oct 18 '22 01:10

Kristjan Liiva