Pokedex on Angular 17

47 Views Asked by At

I'm studying into a Boot Camp and one of past challenge was create a Pokedex with pokeapi. I over the challenge and now I'm going to fix some thinks. I have a strange problem when I'm into details of the single pokemon. When into interpolation I put the various propriety I don't get a problem, into screen stamp it but console log give me and error.

detail.component.ts:79 ERROR TypeError: Cannot read properties of undefined (reading 'type')
    at DetailComponent_Template (detail.component.ts:26:12)
    at executeTemplate (core.mjs:11223:9)
    at refreshView (core.mjs:12746:13)
    at detectChangesInView$1 (core.mjs:12970:9)
    at detectChangesInViewIfAttached (core.mjs:12933:5)
    at detectChangesInComponent (core.mjs:12922:5)
    at detectChangesInChildComponents (core.mjs:12983:9)
    at refreshView (core.mjs:12796:13)
    at detectChangesInView$1 (core.mjs:12970:9)
    at detectChangesInViewIfAttached (core.mjs:12933:5)
@Component({
  selector: 'app-detail',
  standalone: true,
  imports: [DetailComponent],
  template: `
    <div>
      <h2>{{ this.pokemonDetails.name.toUpperCase() }}</h2>
      <img
        src="https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/home/{{
          pokemonId
        }}.png"
        alt="https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/home/{{
          pokemonId
        }}.png"
        height="100"
      />
      <div>
        <p>Pokemon type:</p>
        <p>{{ this.pokemonDetails.types[0].type.name }}</p>
        <p>{{ this.pokemonDetails.types[1].type.name }}</p>
      </div>
      <div>
        <p>
          Description:
          {{ this.speciesDetail.flavor_text_entries[0].flavor_text }}
        </p>
      </div>
      <div>
        <p>BASE STAT:</p>
        <ul>
          <li>
            <p>HP:{{ this.pokemonDetails.stats[0].base_stat }}</p>
          </li>
          <li>
            <p>ATK:{{ this.pokemonDetails.stats[1].base_stat }}</p>
          </li>
          <li>
            <p>DEF:{{ this.pokemonDetails.stats[2].base_stat }}</p>
          </li>
          <li>
            <p>S. ATK:{{ this.pokemonDetails.stats[3].base_stat }}</p>
          </li>
          <li>
            <p>S. DEF:{{ this.pokemonDetails.stats[4].base_stat }}</p>
          </li>
          <li>
            <p>SPEED:{{ this.pokemonDetails.stats[5].base_stat }}</p>
          </li>
        </ul>
      </div>
    </div>
  `,
  styles: ``,
})
export default class DetailComponent implements OnInit {
  pokemonId!: number;
  pokemonDetails!: Pokemon;
  speciesDetail!: Species;

  public service = inject(StateService);

  constructor(private route: ActivatedRoute) {}

  ngOnInit(): void {
    this.route.params.subscribe((params) => {
      this.pokemonId = params['id'];
      this.service.getPokemonById(this.pokemonId).subscribe((pokemon) => {
        this.pokemonDetails = pokemon;
        console.log(this.pokemonDetails);
      });
      this.service
        .getPokemonSpeciesDetailsById(this.pokemonId)
        .subscribe((pokemon) => {
          this.speciesDetail = pokemon;
          console.log(this.speciesDetail);
        });
    });
  }
}

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable, forkJoin } from 'rxjs';
import { map, mergeMap } from 'rxjs/operators';
import { Pokemon, PokemonResults } from '../../model/pokemon';

@Injectable({
  providedIn: 'root',
})
export class StateService {
  private pokemonListSubject: BehaviorSubject<Pokemon[]> = new BehaviorSubject<
    Pokemon[]
  >([]);
  public pokemonList$: Observable<Pokemon[]> =
    this.pokemonListSubject.asObservable();

  constructor(private http: HttpClient) {}

  fetchPokemonList(offset: number = 0, limit: number = 20): Observable<void> {
    const url = `https://pokeapi.co/api/v2/pokemon/?offset=${offset}&limit=${limit}`;
    return this.http.get<PokemonResults>(url).pipe(
      map((data: PokemonResults) => data.results),
      mergeMap((pokemonResults) =>
        this.fetchDetailedPokemonData(pokemonResults)
      ),
      map((detailedPokemonList) => {
        this.pokemonListSubject.next(detailedPokemonList);
      })
    );
  }

  private fetchDetailedPokemonData(
    pokemonList: { name: string; url: string }[]
  ): Observable<Pokemon[]> {
    return forkJoin(
      pokemonList.map((pokemon) => this.http.get<Pokemon>(pokemon.url))
    );
  }
  getPokemonById(id: number): Observable<Pokemon> {
    const url = `https://pokeapi.co/api/v2/pokemon/${id}`;
    return this.http.get<Pokemon>(url);
  }

  getPokemonSpeciesDetailsById(id: number): Observable<any> {
    const url = `https://pokeapi.co/api/v2/pokemon-species/${id}`;
    return this.http.get<any>(url);
  }
}

Image

I hope someone can help me because some pokemon display and some no! i'm going crazy to found the problem!

<h2>{{ this.pokemonDetails.name?.toUpperCase() }}</h2>

I try somethings like this but orange alert appears. I'm newbie into the world of programming.

1

There are 1 best solutions below

0
Nick Felker On

The issues are happening as it tries to find some information from a null object and throws. My guess is that your template is trying to load the template before pokemonDetails is fully defined, and so it cannot find the values of certain fields.

You can wrap your template in *ngIf to not show until the value is defined:

<div *ngIf="this.pokemonDetails">
  ...
</div>

Alternatively, you can use nullish operators to only go deeper in an object if the object is defined, ie.

      <div>
        <p>Pokemon type:</p>
        <p>{{ this.pokemonDetails?.types[0]?.type?.name }}</p>
        <p>{{ this.pokemonDetails?.types[1]?.type?.name }}</p>
      </div>

Keep in mind that your template loads immediately and your service does not. The layout in the interim needs to be able to understand what to show.