Angular 5公式Tutorialの作例を改善する

Angularの公式[Tutorial](https://angular.io/tutorial)は、全体をとおしてひとつの作例「[Angular Example - Tour of Heroes: Part 6 ](https://stackblitz.com/angular/oarvopmjgjv)」をつくります。この解説を下じきに、もっと細かくコードの動きが確かめられるように、説明の仕方やサンプルも改めて「[Angular 5入門](http://www.fumiononaka.com/Business/index.html#Angular_5)」を書きました。そのとき、公式の作例にいくつか気づいて直した点があります。それらをご紹介しましょう。

#要らないコードを除く
「Routing」の「[Remove dead code (optional)](https://angular.io/tutorial/toh-pt5#remove-dead-code-optional)」は、書き直しの結果要らなくなったコードを除いています。これに加えてもうひとつ削った方がよいのは、[HeroDetailComponentクラス](https://angular.io/tutorial/toh-pt5#herodetailcomponent)のデコレータ関数`Input()`です。

```typescript
import { Component, OnInit, Input } from '@angular/core';

export class HeroDetailComponent implements OnInit {
	@Input() hero: Hero;

}
```

コンポーネントの詳細情報(hero-detail.component)をリスト表示(heroes.component)から分け、選択されたデータをサービスから得るようにしたからです。親だったリスト表示のコンポーネントから参照を得るためにプロパティ(hero)に添えていたデコレータ関数Input()は意味がなくなります。

```typescript
import { Component, OnInit} from '@angular/core';

export class HeroDetailComponent implements OnInit {
	hero: Hero;

}
```

公式Tutorialにもこの修正が加えられそうです(「[refactor(aio): refining code of tutorial 7 routing](https://github.com/angular/angular/pull/22151)」)。

#リストを空にするとデータが加えられない
まず、リスト表示の画面ですべての項目を削除します(図001)。

####図001■リスト項目がなくなった
![1802001_001.png](https://qiita-image-store.s3.amazonaws.com/0/128949/91df3065-daa4-eaad-0a71-40fee060d2eb.png)

つぎに、新たな項目をテキストフィールドに入力して、[add]ボタンを押します(図002)。

####図002■新たな項目を入力する
![1802001_002.png](https://qiita-image-store.s3.amazonaws.com/0/128949/f333b699-359a-ffb6-d55f-be5e0d4e72b3.png)

すると、番号もテキストも空の項目が加えられます(図003)。ブラウザのコンソールを確かめると、つぎのようなエラーが示されました。

```
body: {error: "Collection 'heroes' id type is non-numeric or unknown. Can only generate numeric ids."}
```

####図003■番号とテキストが空の項目
![1802001_003.png](https://qiita-image-store.s3.amazonaws.com/0/128949/5df141bb-8f33-ec8f-f2c6-9f52cdddc1f4.png)

データの配列が空になって、プロパティidをもつオブジェクトがなくなると、新たな番号はつくれず、データも加えられないようです。

[`HttpClient.put()`メソッド](https://angular.io/api/common/http/HttpClient#put)を使えば、データにid番号を定めて追加できます。リストが空のときは、つぎのようにidは初期値にして`HttpClient.put()`メソッドでデータを送ればよいでしょう。

```typescript
export class HeroService {

	// addHero(hero: Hero): Observable<Hero> {
	addHero(hero: Hero, numHeros: number): Observable<Hero> {
		let heroOvserbable;
		// return this.http.post<Hero>(this.herosUrl, hero, httpOptions)
		if (numHeros === 0) {
			hero.id = 11;
			heroOvserbable = this.http.put(this.herosUrl, hero, httpOptions)
		} else {
			heroOvserbable = this.http.post<Hero>(this.herosUrl, hero, httpOptions);
		}
		return heroOvserbable

	}

}
```

ただし、サービスのクラス(HeroService)はデータの配列を知りません。ですから、リスト表示クラス(HerosComponent)がデータ追加(add()メソッド)のためにサービス(HeroService)のメソッド(addHero())を呼び出すとき、第2引数としてデータ(heros)の長さ(`Array.length`プロパティ)を渡します。

```typescript
export class HerosComponent implements OnInit {

	add(name: string): void {

		// this.heroService.addHero({name} as Hero)
		this.heroService.addHero({name} as Hero, this.heros.length)

	}

}
```

これで、リストを空にしても新たな項目が加えられます。これらの手直しをした作例は「[Angular Example - Tour of Heroes: Part 6 revised](https://stackblitz.com/edit/angular-5-tutorial-example)」に掲げました。

#Angular in-memory-web-apiモジュールのメソッドをオーバーライドする
リストを空にすると新たな項目がつくれないのは、[Angular in-memory-web-api module](https://github.com/angular/in-memory-web-api#angular-in-memory-web-api)の仕様にもとづくようです。デフォルトのid番号を返す[`BackendService.genIdDefault()`メソッド](https://github.com/angular/in-memory-web-api/blob/f7e216d626207f5a553aa710463b5d06b39b8c43/src/in-mem/backend.service.ts#L402-L419)は、[`BackendService.isCollectionIdNumeric()`](https://github.com/angular/in-memory-web-api/blob/f7e216d626207f5a553aa710463b5d06b39b8c43/src/in-mem/backend.service.ts#L509-L517)メソッドで、リストのデータがあり、さらに数値のidプロパティをもつかどうか確かめます。そうでなければ、デフォルトのid番号はできず、エラーになるのです。

データにid番号を振るのは[`BackendService.genId()`メソッド](https://github.com/angular/in-memory-web-api/blob/f7e216d626207f5a553aa710463b5d06b39b8c43/src/in-mem/backend.service.ts#L386-L400)です。このメソッドは、InMemoryDataServiceクラス(in-memory-data.service)でオーバーライドできます(「[Tutorial example has a problem to enter new item into an empty list](https://github.com/angular/angular/issues/22120#issuecomment-364632192)」参照)。すると、前項のようにサービス(HeroService)やリスト表示(HerosComponent)のクラスは書き替えずに済むのです。

InMemoryDataServiceクラスに定めてオーバーライドするつぎのgenId()メソッドは、データがあればid番号の最大値 + 1、なければ初期値を返します。この作例は"[Angular Example - Tour of Heroes: Part 6](https://stackblitz.com/edit/angular-8r8msa?file=src%2Fapp%2Fin-memory-data.service.ts)"に掲げられています。


```typescript
export class InMemoryDataService implements InMemoryDbService {

	genId(heroines: Heroine[]): number {
		return heroines.length > 0 ? Math.max(...heroines.map(heroine => heroine.id)) + 1 : 11;
	}
}
```

#おまけ
細かいところで、もうひとつつけ加えておきます。サービスのクラス(HeroService)の検索メソッド(searchHeroes())で、URLを直書きしているところです。もとのパスはプロパティ(heroesUrl)に定めてあります。後あとの修正も考えるなら、プロパティを用いるべきでしょう。

```typescript
export class HeroService {

	searchHeroes(term: string): Observable<Hero[]> {

		const url = `${this.heroesUrl}/?name=${term}`;
		// return this.http.get<Hero[]>(`api/heroes/?name=${term}`)
		return this.http.get<Hero[]>(url)

	}

}
```