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

## Cannot read property '****' of undefined 
apiからデータを取得して表示させようとしたところ、以下のようなエラーが発生しました。
該当の'name'プロパティは表示されるものの、エラーが出るのは気持ち悪いと感じたので、解決法を
調べました。
![スクリーンショット 2019-12-13 16.37.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512332/fc67e340-787a-a2ea-4b66-0224e9b73be4.png)

## Version
Angular CLI: 8.3.20
Node: 10.12.0
OS: darwin x64
Angular: 8.2.13


## 問題のコード
```typescript: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);
  }
}
```
```typescript:app-routing.module.ts
// 省略
import { HogeComponent } from './hoge/hoge.component';

const routes: Routes = [
// 省略
  {
    path: 'hoge/:id',
    component: HogeComponent
  }
];

// 省略
```
```typescript: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;
      });
  }
}
```
```typescript:hoge.component.html
{{ item.name }}
```

## 考えられる原因
- `undefined`となっているオブジェクトのタイプミス　　　 →　　 表示はされているので該当せず
- 必要なデータの取得が完了する前にレンダリングしている 　 ←　　こっちでした

## 解決策1　「?」 演算子を使う
- 正式名称を [safe navigation operator](https://angular.io/guide/template-syntax#safe-navigation-operator) というそうです
- 演算子の前のオブジェクトが`null`または`undefined`の場合それ以降は評価しません
- `a?.b?.c?.d`と続けて利用可能です

今回の場合、`{{ item?.name }}`とするとエラーは表示されなくなりました。
とはいえ、エラーの出る可能性のあるオブジェクトに毎回`?`をつけるのは手間ですので、次の方法を使いました。

## 解決策2　Resolve を使う
- [Resolveインターフェース](https://angular.jp/api/router/Resolve)を実装したサービスを作成し、routingの設定にresolveオブジェクトを追加します
- これにより、必要なデータの取得を待ってからレンダリングさせることができます

### 手順
#### resolverサービスの作成
```bash
ng g s hoge-resolver
```
名前は何でもいいと思います。ここでは`hoge-resolver`としました。
以下のコードが自動生成されます。

```typescript:hoge-resolver.service.ts
import { Injectable } from '@angular/core';

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

  constructor() { }
}
```

#### Resolveインターフェースの実装
```typescript: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オブジェクトを追加
```typescript: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`を参照できます。

```typescript: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](https://angular.jp/guide/router#milestone-5-route-guards)のうちのひとつみたいなので、他の機能も機会があれば学びたいです。

## 参考
[Angular公式 fetch-data-before-navigating](https://angular.jp/guide/router#fetch-data-before-navigating)