简体   繁体   中英

How do you turn an Observable array of objects, into an Observable of the data inside that array of objects?

I'm getting this error:

Type 'Observable<Country[]>' is not assignable to type 'Observable'. Type 'Country[]' is missing the following properties from type 'Country': name, tld, alpha2Code, alpha3Code, and 20 more.ts(2322


I think it's because the endpoint im reaching serves back an array with an object inside of it. Here's an example.

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


This is the code I can't get to work. It refuses to compile and gives me the error I posted above.

export class DetailsComponent implements OnInit {
  country$!: Observable<Country>
  countryBorders$!: Observable<Country[]>

  constructor(private apiService: ApiService, private activatedRoute: ActivatedRoute){}

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

The getCountryByName() method looks like this:

  getCountryByName(name: string) {
    return this.http
      .get<Country[]>(`${this.api}/name/${name}`)
  }

How do I get the "this.country$" variable/observable/subject (idk what it is technically), to hold the data thats inside the object thats inside of the array that's being returned by the HTTP request?

I thought I could map the values out of the array, but this doesn't work either:

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

When I do that, I don't get the error but this template:

<div *ngIf="country$ | async as country">
  <h1>{{country$}}</h1>
</div>

renders

[object Object]

...like the h1 on the page literally just says "[object Object]"


What am I doing wrong? What combination of operators do I need to use to convert the 'Observable<Country[]>' into just 'Observable<Country' so I can render it inside an html template like this?

  <div>
    <p><strong>Native Name: </strong>{{ country$.name.nativeName }}</p> 
    <p><strong>Population: </strong>{{ country$.population | number : '0.0' }}</p> 
    <p><strong>Region: </strong>{{ country$.region }}</p> 
    <p><strong>Sub Region: </strong>{{ country$.subregion }}</p> 
    <p><strong>Capital: </strong>{{ country$.capital }}</p></div>
    <div> 
      <p><strong>Top Level Domain: </strong>{{ country$.tld }}</p> 
      <p><strong>Currencies: </strong>{{ country$.currencies }}</p> 
      <p><strong>Languages: </strong>{{ country$.languages }}</p>
   </div>
</div>

This is the interface in case it's relevant to the answer:

export interface Country {
  name: Name; //---
  tld: string[]; //---
  cca2: string;
  ccn3: string;
  cca3: string;
  cioc: string;
  independant: Boolean
  status: string;
  unMember: Boolean;
  currencies: Currency[]; //---
  idd: Idd;
  capital: string[]; //---
  altSpellings: string[];
  region: string;
  subregion: string; //---
  languages: any; //---
  translations: any;
  latlng: number[];
  landlocked: Boolean;
  borders: string[]; //---
  area: number;
  demonyms: any;
  flag: string;
  maps: Maps;
  population: number; //---
  gini: any;
  fifa: string;
  car: any;
  timezones: string[];
  continents: string[];
  flags: Flags;
  coatOfArms: COA;
  startOfWeek: string;
  capitalInfo: Capital;
  postalCode: Postal;
  // nativeName: string;
  // numericCode: string;
  // regionalBlocs: RegionalBloc[];
}

Looks like you have a typing issue in your getCountryByName method. Instead of .get<Country[]> , it should be .get<Country> :

getCountryByName(name: string) {
  return this.http
    .get<Country>(`${this.api}/name/${name}`)
}

The RestCountries API returns the response with an array type. You can achieve with extract the first element of the array via map rxjs.

getCountryByName(name: string): Observable<Country> {
  return this.http
    .get<Country[]>(`${this.api}/name/${name}`)
    .pipe(map((x) => x[0]));
}

The way you extract the properties from the country$ Observable is incorrect. You should look for an async pipe as what you have achieved in the question.

<div *ngIf="country$ | async as country">
  <div>
    <p><strong>Native Name: </strong>{{ country.name.nativeName | json }}</p>
    <p>
      <strong>Population: </strong>{{ country.population | number: '0.0' }}
    </p>
    <p><strong>Region: </strong>{{ country.region }}</p>
    <p><strong>Sub Region: </strong>{{ country.subregion }}</p>
    <p><strong>Capital: </strong>{{ country.capital }}</p>
  </div>
  <div>
    <p><strong>Top Level Domain: </strong>{{ country.tld }}</p>
    <p><strong>Currencies: </strong>{{ country.currencies }}</p>
    <p><strong>Languages: </strong>{{ country.languages }}</p>
  </div>
</div>

Demo @ StackBlitz

This was the correct format:

export class DetailsComponent implements OnInit {
  country$!: Observable<Country>
  countryBorders$!: Observable<Country[]>

  constructor(private apiService: ApiService, private activatedRoute: ActivatedRoute){}

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

I was doing it right. But in the template use:

<div *ngIf="country$ | async as country">
  <h1>{{country.name.common}}</h1>
</div>

NOT

<div *ngIf="country$ | async as country">
  <h1>{{country$.name.common}}</h1>
</div>

dumb mistake

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM