Cannot read property '****' of undefined
apiからデータを取得して表示させようとしたところ、以下のようなエラーが発生しました。
該当の'name'プロパティは表示されるものの、エラーが出るのは気持ち悪いと感じたので、解決法を
調べました。
Version
Angular CLI: 8.3.20
Node: 10.12.0
OS: darwin x64
Angular: 8.2.13
問題のコード
hoge.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
// 省略
export class HogeService {
constructor(private http: HttpClient) { }
getItem(id: string): Observable<any> {
return this.http.get<any>('https://fugafuga.com/api/v2/items/' + id);
}
}
app-routing.module.ts
// 省略
import { HogeComponent } from './hoge/hoge.component';
const routes: Routes = [
// 省略
{
path: 'hoge/:id',
component: HogeComponent
}
];
// 省略
hoge.component.ts
import { Component, OnInit } from '@angular/core';
import { HogeService } from '../hoge.service';
import { ActivatedRoute} from '@angular/router';
// 省略
export class HogeComponent implements OnInit {
item: any;
constructor(private route: ActivatedRoute, private hogeService: HogeService) { }
ngOnInit() {
const id = this.route.snapshot.paramMap.get('id');
this.hogeService.getItem(id).subscribe(
item => {
this.item = item;
});
}
}
hoge.component.html
{{ item.name }}
考えられる原因
-
undefined
となっているオブジェクトのタイプミス → 表示はされているので該当せず - 必要なデータの取得が完了する前にレンダリングしている ← こっちでした
解決策1 「?」 演算子を使う
- 正式名称を safe navigation operator というそうです
- 演算子の前のオブジェクトが
null
またはundefined
の場合それ以降は評価しません -
a?.b?.c?.d
と続けて利用可能です
今回の場合、{{ item?.name }}
とするとエラーは表示されなくなりました。
とはいえ、エラーの出る可能性のあるオブジェクトに毎回?
をつけるのは手間ですので、次の方法を使いました。
解決策2 Resolve を使う
- Resolveインターフェースを実装したサービスを作成し、routingの設定にresolveオブジェクトを追加します
- これにより、必要なデータの取得を待ってからレンダリングさせることができます
手順
resolverサービスの作成
ng g s hoge-resolver
名前は何でもいいと思います。ここではhoge-resolver
としました。
以下のコードが自動生成されます。
hoge-resolver.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class HogeResolverService {
constructor() { }
}
Resolveインターフェースの実装
hoge-resolver.service.ts
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { HogeService } from './hoge.service';
@Injectable({
providedIn: 'root'
})
export class HogeResolverService implements Resolve<any> {
constructor(private hogeService: HogeService) { }
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const id = route.paramMap.get('id');
return this.hogeService.getItem(id);
}
}
routingの設定にresolveオブジェクトを追加
app-routing.module.ts
// 省略
import { HogeComponent } from './hoge/hoge.component';
const routes: Routes = [
// 省略
{
path: 'hoge/:id',
component: HogeComponent,
resolve: {
item: HogeResolverService
}
}
];
// 省略
ここで、resolveの中身のitem
は後で参照するための名前、HogeResolverService
は先程作成したサービスです。
コンポーネントで参照する
ActivatedRoute.data
をsubscribeして先程のitem
を参照できます。
hoge.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute} from '@angular/router';
// 省略
export class HogeComponent implements OnInit {
item: any;
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.route.data.subscribe(data => this.item = data.item);
}
}
以上でエラーが解決しました。
おわりに
Resolveはroutingを制御するroute-guardsのうちのひとつみたいなので、他の機能も機会があれば学びたいです。