2

I think I have read 100+ posts on the topic, and I still cannot figure out how to chain two HttpClient calls using rxjs in Angular 6.

Let's say I have a service with that signature:

GeoService {
  getState(): Observable<string> {
    return this.http.get<string>(stateURL);
  }
  getCities(state: string): Observable<string[]> {
    return this.http.get<string[]>(citiesURL + state);
  }
}

I can't for the life of me figure out how to obtain both the state and the corresponding list of cities in my component:

import { Observable } from 'rxjs';
import { map, flatMap, mergeMap, filter, switchMap } from 'rxjs/operators';

...

ngOnInit() {
  this.svc.getState().
  pipe(map((state) => {
      this.state = state;
      return this.svc.getCities(state);
    }),
    mergeMap((cities) => this.cities = cities))
).subscribe(console.log('done'));

The code above in one of my 20 random attempts at combining pipe/map/mergeMap/subscribe in every way I could think of... a working example would be really really appreciated :)

Thanks!

Edit: None of the "possible duplicate" posts contain an actual example that works

15
  • you could chain them by putting the second (and third and so on) call within the res part (completion if you dont need the res value) of the subscribe to the previous call. eg: this.svc.getState().subscribe(res=>{this.svc.getCities(res).subscribe(res=>{},err=>{},()=>{})},err=>{},()=>{}) Commented Nov 30, 2018 at 9:24
  • Possible duplicate of Chaining Observables in RxJS Commented Nov 30, 2018 at 9:29
  • @ggradnig i wouldnt say so, you can do it from the subscribe part of the http call Commented Nov 30, 2018 at 9:31
  • this.svc.getState().pipe(mergeMap(state => this.svc. getCities(state))).subscribe(...) Commented Nov 30, 2018 at 9:31
  • @martin you need to subscribe to the getCities() call else it wont actually make the http call Commented Nov 30, 2018 at 9:32

3 Answers 3

2

The 21st attempt would have been correct ;-)

this.svc.getState().
    pipe(mergeMap((state) => {
        this.state = state;
        return this.svc.getCities(state);
    }),
    tap((cities) => this.cities = cities)))
.subscribe(() => console.log('done'));

The chained Observable goes inside mergeMap. You can think of it as:

First, map the incoming notifaction to an Observable, then merge the resulting "inner" Observable into the "outer" Observable

Also, use tap instead of map if you intend to change an outside state.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, I tested our solution and it works. I accepted Kevins' answer since it is simpler, and it doesn't change state from inside mergeMap.
2

You were almost there:

    this.svc.getState().
        pipe(
            mergeMap((state) => {
                return this.svc.getCities(state).pipe(map(cities => {
                    return { state: state, cities: cities }
                }));
            }),
        ).subscribe(stateAndCities => console.log(stateAndCities));

I advise you to read this article:

https://blog.strongbrew.io/rxjs-best-practices-in-angular/#using-pure-functions

It also explains why you shouldnt interact with global variables in rxjs operators.

3 Comments

Thanks, tested and it works. I accepted Kevin's answer since it doesn't involve an intermediate object.
I do understand your point about pure functions, but I don't like having to aggregate into a meaningless object just to carry results to the end of the pipe. A pattern that would be natural to me would be: var response = this.svc.getState(); response.subscribe(state => this.state = state); response.pipe(map(state => getProviders(state))).subscribe(cities => ...); Pseudo-code, and I'm so fed up with that API that I don't have the courage to test it...
That would work, but your response object would have to be a ReplaySubject (or an Observable with publishReplay() applied)
1

You can do something like this

this.svc.getState().pipe(
     tap(state=>this.state=state),
     switchMap(this.svc.getCities))
.subscribe(cities=>{
    //got the cities
})

the map operator is here to transform the emited value, but the tap operator is used to do something without modifying emited value of the observable.

note that switchMap(this.svc.getCities) is equivalent to switchMap(state=>this.svc.getCities(state)

6 Comments

You are assigning a global variable in an rxjs operator. That is a bit of an anti-pattern ...
a global valiable ? where ?
this.state? Your rxjs operators should ideally use pure functions.
ok but the tap ( new version of "do" ) operator is made for side-effect rxjs-dev.firebaseapp.com/api/operators/tap
Correct. But its still an anti-pattern ;) And in this case, is easily solved using a standard map operator.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.