LoginSignup
14
13

More than 3 years have passed since last update.

Angularで必要なデータの取得を待ってからレンダリングさせる[Resolve]

Last updated at Posted at 2019-12-14

Cannot read property '****' of undefined

apiからデータを取得して表示させようとしたところ、以下のようなエラーが発生しました。
該当の'name'プロパティは表示されるものの、エラーが出るのは気持ち悪いと感じたので、解決法を
調べました。
スクリーンショット 2019-12-13 16.37.04.png

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のうちのひとつみたいなので、他の機能も機会があれば学びたいです。

参考

Angular公式 fetch-data-before-navigating

14
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
13