Angularで参照に頼らずに配列やオブジェクトの変更を検知したい! [IterableDiffers]

## IterableDiffers? KeyValueDiffers?
なんだそれは…という人向けの記事です。

## 配列やオブジェクトの変更を検知するには
あるコンポーネントのInput()がオブジェクトや配列だった時に、その変更をトリガーにして処理を走らせたい場合、どうしますか？例えばこんな感じ。

```js:AppComponent.ts
export class AppComponent  {
  novels = [
    { author: 'Emily', publisher:'BananaBooks'},
    { author: 'Bob', publisher:'OrangeNews'}
  ];

  addNovel(){
    this.novels.push({ author: 'Lyra', publisher: 'GrapePaper'});
    console.log('addNovel')
  }
}
```
AppComponentでは、小説本の情報をまとめ、novelsとしてBookComponentに渡します。ボタンを押すと小説を追加することができます。BooksComponentでは受け取った書籍情報にコードを追加した物を表示します。ですが、これではAppComponent.tsで`AddNovel()`が呼ばれてもnovelsの参照が変わらないため、テンプレートが更新されません。

```js:BooksComponent.ts
export interface BookInfo{
  author:string;
  publisher:string;
}

interface BookDetail extends BookInfo{
  code: number;
}

export class BooksComponent implements OnInit {
  @Input() novels: BookInfo[];
  _novels: BookDetail[];

  ngOnInit() {
    this.init();
  }

  init(){
    this._novels = this.fetchCodes(this.novels);
  }

  fetchCodes(books: BookInfo[]): BookDetail[]{
    // it should be service in real case
    return books.map(book => {
      const randomNum = Math.floor( Math.random() * 101 );
      return Object.assign({code: randomNum}, book);
    });
  }
}
```
```html:BooksComponent.html
<section>
  <h1>novel</h1>
  <div class="novel" *ngFor="let novel of _novels">
    <div class="author">author: {{novel.author}}</div>
    <div class="publisher">publisher: {{novel.publisher}}</div>
    <div class="code">code:{{novel.code}}</div>
  </div>
</section>
```

# よくみる手法
よく見るのが、参照の変更をトリガーにするという手法です。例えばこんな感じ。

```js:BooksComponent.ts
export class BooksComponent implements OnChanges {
  @Input() novels: BookInfo[];
  _novels: BookDetail[];

  ngOnChanges(sc:SimpleChanges) {
    const novelChange = sc['novels'];
    this.init();
  }

  init(){
    this._novels = this.fetchCodes(this.novels);
  }
}
```
こうすると、AppComponent側でnovelsの参照を新しくしてあげればngOnChangesが走るので無事init処理を走らせることができます。逆に言えば、**BooksComponentを使う側はpushした後に参照を新しくする処理をしなければならない**ということです。

```js:Appcomponent.ts
addNovel(){
    this.novels.push({ author: 'Lyra', publisher: 'GrapePaper'});
    this.novels = this.novels.concat([]);
}
```
ちょっとした手間ですが、使う側がみんなその処理をしなければいけないと考えるとそこそこ不便です。できれば使う側は追加の処理をするだけで済むようにしたいですよね。そこで登場するのが**IterableDiffers**です。

# IterableDiffersを使う手法
IterableDiffersはその名の通り、iterableの変更を検知します。調べたら英語の記事で「おそらく最も知名度のないAPI」と言われていただけあって日本語の記事がほとんどヒットしません。でもこれがかなり便利なんです。とにかく使ってみましょう。
あとで比較してわかりやすいように、novelsと同じBookInfo[]のcomicsを追加します。そして、変更を検知してくれる`IterableDiffer`も準備します。

```js:BooksComponent.ts
export class BooksComponent implements OnInit {
  @Input() novels: BookInfo[];
  @Input() comics: BookInfo[];
  _novels: BookDetail[];
  _comics: BookDetail[] = [];
  comicDiffer: IterableDiffer<BookInfo>;
```

そして、その`IterableDiffer`を生成するための`IterableDiffers`をDIします。

```js:BooksComponent.ts

  constructor(private iterable: IterableDiffers) { }
```

次に、初期化処理で`IterableDiffer`を生成します。今回は例をわかりやすくするため、参照が〜というのは一旦置いておき、最初のBooksComponentにならいngOnInit()で初期化していきます。

```js:BooksComponent.ts

  ngOnInit() {
    this.comicDiffer = this.iterable.find(this.comics).create();
  }
```

`IterableDiffers`はAngularが検知しない変更を検知するためのライフサイクルフックである`ngDoCheck()`で呼び出します。今回は本を追加するという処理のみを行うため、追加された項目に対してオペレーションができる`forEachAddedItem`を使います。

```js:BooksComponent.ts
  ngDoCheck(){
    const comicChange = this.comicDiffer.diff(this.comics);
    if(comicChange){
      comicChange.forEachAddedItem(record => {
        this._comics = this._comics.concat(this.fetchCodes([record.item]));
      });
    }
  }
```
これで無事、BooksComponentを使う側は普通にcomicsプロパティに項目を追加するだけで使えるようになりました。

```js:AppComponent.ts
  addComic(){
    this.comics.push({ author: 'Haru', publisher: 'MaronInc'});
  }
```
これまでの内容をまとめて簡単なデモを作成しました。
[StackBlitz](https://stackblitz.com/edit/angular-differs)

# 一瞬ハマったこと
`ngDoCheck`は`ngOnInit`、`ngOnChanges`の後に実行されるので、初期化時に入っている情報も`forEachAddedItem`に入ります。なので、`ngDoCheck`手前で`forEachAddedItem`と同じ処理を書いてしまうと二重に実行されてしまいます。

# 当たり前だけどうっかり見落としそうなこと
`IterableDiffer`はあくまでIterableの変更を検知するものなので、今回のように配列の中身がオブジェクトの場合、配列の要素の中の変更までは検知してくれません。今回のケースで中をチェックしたい場合は、**keyValueの変更を検知してくれるKeyValueDiffers**を使うと良いでしょう。基本的な使い方は同じなのでぜひ使ってみてください。