Search code examples
angularapistring-interpolation

Angular interpolation error fetching from api


This is for a project. I need to access an array from this api: https://pokeapi.co/. I am able to access the array, which looks like this:

{"count":1050,"next":"https://pokeapi.co/api/v2/pokemon?offset=20&limit=20","previous":null,"results":[{"name":"bulbasaur","url":"https://pokeapi.co/api/v2/pokemon/1/"},{"name":"ivysaur","url":"https://pokeapi.co/api/v2/pokemon/2/"},{"name":"venusaur","url":"https://pokeapi.co/api/v2/pokemon/3/"},{"name":"charmander","url":"https://pokeapi.co/api/v2/pokemon/4/"},{"name":"charmeleon","url":"https://pokeapi.co/api/v2/pokemon/5/"},{"name":"charizard","url":"https://pokeapi.co/api/v2/pokemon/6/"},{"name":"squirtle","url":"https://pokeapi.co/api/v2/pokemon/7/"},{"name":"wartortle","url":"https://pokeapi.co/api/v2/pokemon/8/"},{"name":"blastoise","url":"https://pokeapi.co/api/v2/pokemon/9/"},{"name":"caterpie","url":"https://pokeapi.co/api/v2/pokemon/10/"},{"name":"metapod","url":"https://pokeapi.co/api/v2/pokemon/11/"},{"name":"butterfree","url":"https://pokeapi.co/api/v2/pokemon/12/"},{"name":"weedle","url":"https://pokeapi.co/api/v2/pokemon/13/"},{"name":"kakuna","url":"https://pokeapi.co/api/v2/pokemon/14/"},{"name":"beedrill","url":"https://pokeapi.co/api/v2/pokemon/15/"},{"name":"pidgey","url":"https://pokeapi.co/api/v2/pokemon/16/"},{"name":"pidgeotto","url":"https://pokeapi.co/api/v2/pokemon/17/"},{"name":"pidgeot","url":"https://pokeapi.co/api/v2/pokemon/18/"},{"name":"rattata","url":"https://pokeapi.co/api/v2/pokemon/19/"},{"name":"raticate","url":"https://pokeapi.co/api/v2/pokemon/20/"}]}

I'm trying to use interpolation, to iterate the names in my template. but since 'name' is wrapped in 'results' on the api, it can only reach it if I use results in the template. This creates an error, and it often doesn't compile as a result.

my ts file

import { Component, OnInit } from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {PokemonData} from '../../models/pokemon';
import {PokemonService} from '../../models/services/pokemon.service';

@Component({
  selector: 'app-pokemon-data',
  templateUrl: './pokemon-data.component.html',
  styleUrls: ['./pokemon-data.component.css']
})
export class PokemonDataComponent implements OnInit {
  // @Input() id: string;
  public pokemonData: PokemonData[] = [];
  private name: string;

  constructor(private pokemonService: PokemonService, private route: ActivatedRoute) {
        this.name = this.route.snapshot.paramMap.get('name');
  }
    onLoadPokemonByName(): void {
    this.pokemonService.getPokemonbyName(this.name).subscribe(
      res => {
        console.log(this.name);
        this.pokemonData = JSON.parse(JSON.stringify(res));
        console.log(this.pokemonData);
      }
    );

  }
  ngOnInit(): void {
    this.onLoadPokemonByName();
  }

}

this is the html file

<p>pokemon works!</p>
<div *ngFor="let res of pokemon.results">
  <div>{{res.name}}, {{res.url}}</div>
  <button routerLink="/pokemondata/{{res.name}}">Pokemon Details</button>
</div>

and this is the service file

import { Injectable } from '@angular/core';
import {Observable} from 'rxjs';
import {map} from 'rxjs/operators';
import {HttpClient} from '@angular/common/http';
import {Pokemon, PokemonData} from '../pokemon';

@Injectable({
  providedIn: 'root'
})
export class PokemonService {

  constructor(private http: HttpClient) {

  }

  // http call
  private baseUrl = 'https://pokeapi.co/api/v2/';

  getPokemonList(): Observable<Pokemon[]> {
    return this.http.get<Pokemon[]>(this.baseUrl + `pokemon?limit=150`);

  }

my class file

export class Pokemon {
  // this class contains properties that contain information on Pokemons
  name: string;
  url: string;
  results: Result[];

}

export interface Result {
  results: Result[];
}

my question: how do I instantiate results so that i5 doesn't cause an error in the template.

additional ts file

import {Component, Inject, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material/dialog';
import {PokemonService} from '../../models/services/pokemon.service';
import {PokemonData} from '../../models/pokemon';
import {Chain, Evolution, Species} from '../../models/evolution';
import {ActivatedRoute} from '@angular/router';

@Component({
  selector: 'app-evolution',
  templateUrl: './evolution.component.html',
  styleUrls: ['./evolution.component.css']
})
export class EvolutionComponent implements OnInit {

evolution: Evolution;
species: Species;
chain: Chain;
private id: string;

  constructor(private pokemonService: PokemonService,
              private route: ActivatedRoute) {
    this.id = this.route.snapshot.paramMap.get('id');

  }

   getEvolutionDetails(): void {
    this.pokemonService.getEvolutionData(this.id).subscribe((
      res => {
        console.log(this.id);
        this.evolution = JSON.parse(JSON.stringify(res));
        console.log(this.evolution);
      }
    ));

   }
  ngOnInit(): void {
    this.getEvolutionDetails();
  }

}

html file

  <p *ngIf="evolution.chain.species">
  <div *ngFor="let res of evolution.chain.species">{{res.name}} </div>

The error I get: Cannot read property 'chain' of undefined and vendor.js:77062 ERROR Error: Cannot find a differ supporting object '[object Object]' of type 'bulbasaur'. NgFor only supports binding to Iterables such as Arrays.


Solution

  • Your error is because pokemon is null when you're trying to do the *ngFor right? In this case you can add ? after pokemon in you HTML.

    <div *ngFor="let res of pokemon?.results">
      <div>{{res.name}}, {{res.url}}</div>
      <button routerLink="/pokemondata/{{res.name}}">Pokemon Details</button>
    </div>
    

    And to be sure that you have result it will be better to add ? after res.

    <div *ngFor="let res of pokemon?.results">
      <div>{{res?.name}}, {{res?.url}}</div>
      <button routerLink="/pokemondata/{{res?.name}}">Pokemon Details</button>
    </div>
    

    Or you can just do a if

    <ng-container *ngIf="pokemon">
      <div *ngFor="let res of pokemon?.results">
        <div>{{res?.name}}, {{res?.url}}</div>
        <button routerLink="/pokemondata/{{res?.name}}">Pokemon Details</button>
      </div>
    </ng-container>
    

    For you second error with your EvolutionComponent you have 2 errors:

      <p *ngIf="evolution.chain.species">
      <div *ngFor="let res of evolution.chain.species">{{res.name}} </div>
    

    To fix them you can do:

      <p *ngIf="evolution?.chain?.species">
      <div *ngFor="let res of evolution?.chain?.species | keyvalue">{{res?.value?.name}} </div>
    

    The ? is to check if the value is not null.

    For the error NgFor only supports binding to Iterables such as Arrays.. It's because the field species is not an array but an object so you have to use the keyvalue pipe. Click here for the documentation