Angular 17 Pokedex详情页控制台报错问题求助
问题:Angular Pokedex详情页控制台报错,部分宝可梦无法正常显示
进入单只宝可梦详情页时,页面能渲染数据,但控制台抛出以下错误:
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)
部分宝可梦无法正常显示。尝试使用可选链操作符:
<h2>{{ this.pokemonDetails.name?.toUpperCase() }}</h2>
但出现橙色警告。相关代码如下:
组件代码(DetailComponent)
@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); }); }); } }
服务代码(StateService)
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); } }
解决方案
1. 核心问题分析
报错原因是数据异步加载导致模板渲染时,pokemonDetails或speciesDetail还未初始化,直接访问其深层属性(如types[0].type.name)会触发undefined读取错误。另外,部分宝可梦可能只有1种属性,直接访问types[1]也会报错。
2. 具体修复步骤
(1)初始化对象,避免undefined
组件中声明的pokemonDetails和speciesDetail用了非空断言!,但初始状态仍为undefined,建议初始化为空对象或对应类型的默认值:
pokemonDetails: Pokemon = {} as Pokemon; speciesDetail: Species = {} as Species;
(2)在模板中正确使用可选链和空值合并操作符
- 移除模板中的
this.,Angular模板中直接访问组件属性即可,不需要this,这也是使用可选链出现橙色警告的原因。 - 对所有深层属性使用可选链
?.,防止访问undefined属性;对可能不存在的项(如第二个属性),用空值合并??提供默认值。
修改后的模板部分:
<div> <h2>{{ pokemonDetails.name?.toUpperCase() ?? 'Loading...' }}</h2> <img src="https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/home/{{ pokemonId }}.png" alt="Pokemon sprite" height="100" /> <div> <p>Pokemon type:</p> <p>{{ pokemonDetails.types?.[0]?.type?.name ?? 'N/A' }}</p> <p>{{ pokemonDetails.types?.[1]?.type?.name ?? 'N/A' }}</p> </div> <div> <p> Description: {{ speciesDetail.flavor_text_entries?.[0]?.flavor_text ?? 'No description available' }} </p> </div> <div> <p>BASE STAT:</p> <ul> <li><p>HP:{{ pokemonDetails.stats?.[0]?.base_stat ?? 0 }}</p></li> <li><p>ATK:{{ pokemonDetails.stats?.[1]?.base_stat ?? 0 }}</p></li> <li><p>DEF:{{ pokemonDetails.stats?.[2]?.base_stat ?? 0 }}</p></li> <li><p>S. ATK:{{ pokemonDetails.stats?.[3]?.base_stat ?? 0 }}</p></li> <li><p>S. DEF:{{ pokemonDetails.stats?.[4]?.base_stat ?? 0 }}</p></li> <li><p>SPEED:{{ pokemonDetails.stats?.[5]?.base_stat ?? 0 }}</p></li> </ul> </div> </div>
(3)优化异步数据加载逻辑
用forkJoin合并两个API请求,确保数据都加载完成后再更新视图,避免部分数据先到导致的渲染问题:
ngOnInit(): void { this.route.params.subscribe((params) => { this.pokemonId = params['id']; forkJoin([ this.service.getPokemonById(this.pokemonId), this.service.getPokemonSpeciesDetailsById(this.pokemonId) ]).subscribe(([pokemon, species]) => { this.pokemonDetails = pokemon; this.speciesDetail = species; }); }); }
(4)添加加载状态提示
在模板顶部添加加载状态,当数据未加载完成时显示:
<div *ngIf="!pokemonDetails.name && !speciesDetail.flavor_text_entries"> Loading Pokemon details... </div> <div *ngIf="pokemonDetails.name || speciesDetail.flavor_text_entries"> <!-- 原有详情内容 --> </div>
3. 额外注意事项
- Angular模板中不需要使用
this.访问组件属性,这是TypeScript代码中的写法,模板直接写属性名即可,否则会触发警告。 - 非空断言
!仅告诉TypeScript变量会被初始化,但运行时如果未初始化仍会报错,所以初始化空对象更可靠。 - 部分宝可梦(如百变怪)只有1种属性,
types[1]不存在,必须用可选链或条件判断处理。
内容的提问来源于stack exchange,提问作者Daniele Quintiliani
相关产品推荐
相关产品推荐

