Home > Mobile >  How do you create an Observable AND store values from that Observable into a different Observable at
How do you create an Observable AND store values from that Observable into a different Observable at

Time:12-28

I have 2 Observables:

  country$!: Observable<Country>
  countryBorders$!: Observable<Country[]>

Im trying to retrieve the "country$" data from a HTTP GET request to here (in this example the country is USA):

https://restcountries.com/v3.1/name/usa

and after I retrieve the particular countries data, I'd like to store the "borders" property array from that data into the "countryBorders$" observable.


Right now I am doing it like this:

  getCountriesByCodes(codes: string[]) {
   return this.http.get<Country[]>(
  `${this.api}alpha?codes=${codes.join(',')}`
);
}

 ngOnInit(): void {
    this.activatedRoute.params.subscribe((params) => {
      let countryName = params['country'];
      this.country$ = this.apiService.getCountryByName(countryName).pipe(map(([info])=>{
        this.countryBorders$ = this.apiService.getCountriesByCodes(info.borders);
        return info;
      }))
    });
    };

but I've been told you shouldn't create or store Observable values in the subscribe function of another Observable.


What am I supposed to use here instead? mergeMap? switchMap? concatMap? Do I just replace "map" with one of those? Can someone point me in the right direction?

CodePudding user response:

Indeed nesting observables isn't recommended. You can simply update with mergeMap:

ngOnInit(): void {
  this.activatedRoute.params.pipe(
    mergeMap((params) => {
      let countryName = params['country'];
      return this.apiService.getCountryByName(countryName).pipe(
        tap(([info]) => {
          this.countryBorders$ = this.apiService.getCountriesByCodes(info.borders);
        })
      );
    })
  ).subscribe(([info]) => {
    this.country$ = of(info);
  });
}

CodePudding user response:

but I've been told you shouldn't create or store Observable values in the subscribe function of another Observable.

You should not have nested subscriptions. If you have one, that means whenever the first Observable emits a value, you create a new subscription to the second Observable.

You can do something like this:

this.activatedRoute.params.subscribe((params) => {
  let countryName = params['country'];
  this.country$ = this.apiService.getCountryByName(countryName);
  this.countryBorders$ = this.country$.pipe(map(([info]) => 
    this.apiService.getCountriesByCodes(info.borders)))
  });
};

CodePudding user response:

With observables, you don't really "store to another observable", but rather declare an observable to start from another observable. In your case countryBorders$ would be defined from country$. So, you could do something like this:

ngOnInit(): void {
    this.activatedRoute.params.subscribe(params => {
        let countryName = params['country'];
        this.country$ = this.apiService.getCountryByName(countryName);
        this.countryBorders$ = this.country$.pipe(
            switchMap(([info]) => this.apiService.getCountriesByCodes(info.borders))
        );
    });
}

Notice how countryBorders$ is defined to start with the emissions from country$, then pipes the emission to a call to getCountriesByCodes(). switchMap is used to "flatten the observable", meaning that instead of using plain map, which would emit type Observable<T>. switchMap will subscribe to the "inner observable" and emit its emissions, so you get an emission of type T.


The above code will work, but we can simplify this even more in a way that doesn't require ngOnInit nor need to explicitly call .subscribe().

Let's first start by defining the countryName as an observable:

private countryName$ = this.activatedRoute.params.pipe(params => params['country']);

Now, we can define country$ to start with this observable:

private country$ = this.countryName$.pipe(
    switchMap(countryName => this.apiService.getCountryByName(countryName))
);

And finally countryBorders$:

public countryBorders$ = this.country$.pipe(
    switchMap(([info]) => this.apiService.getCountriesByCodes(info.borders))
);

Now you have an observable (countryBorders$) that will emit exactly the border data that you want. You can cleanly consume this observable in your template using the async pipe:

<div *ngIf="countryBorders$ | async as borders">
  <!-- use `borders` here -->
</div>

CodePudding user response:

Here you can try this logic to merge two observables :

import { map, merge, Observable, tap } from 'rxjs';

const observable$ = new Observable((subscriber) => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);

  setTimeout(() => {
    subscriber.next(4);
  }, 4000);
});

const observable2$ = new Observable((subscriber) => {});

// here we merge two observables
let final$ = merge(observable$, observable2$);

final$.subscribe({
  next(x) {
    console.log(x);
  },
});

  • Related