【Angular】なぜTemplateから関数を直接呼び出すとパフォーマンスの低下を起こすのか、とその対策

## はじめに
よくAngularのパフォーマンス改善で言われている「Templateからメソッドを直接呼んではいけない」はなぜなのか、を[こちらの記事](https://medium.com/showpad-engineering/why-you-should-never-use-function-calls-in-angular-template-expressions-e1a50f9c0496)を参考に実際に試して確認、そしてその対策方法を共有する。

## 注意事項
今回は`Pipes`をメインに紹介したいため、`@Input()`と`ChangeDetectionStrategy.OnPush`を使った対策は割愛する。

## 今回使っていくソースコード
1~100までの数値とその数値が偶数かどうかを表示する。
数値の横のボタンがクリックされると、その数値を100倍にして更新する。

```typescript:call-function-from-template.component.ts
import { Component, DoCheck } from '@angular/core';

@Component({
    selector: 'app-call-function-from-template',
    templateUrl: './call-function-from-template.component.html',
    styleUrls: ['./call-function-from-template.component.scss'],
})
export class CallFunctionFromTemplateComponent implements DoCheck {
    public numberList = Array.from({ length: 100 }).map((_, i) => i + 1);

    constructor() {}

    // ChangeDetectionを確認
    ngDoCheck(): void {
        console.log('CallFunctionFromTemplateComponent-DoCheck');
    }

    // その数値が偶数であればTrue, 奇数であればFalseを表示する。
    // 今回Templateから呼ばれるメソッド
    public isEven(n: number) {
        console.log('Function isEvent Called');
        return n % 2 === 0 ? 'True' : 'False';
    }

    // その数値を100倍して表示する
    public changeNumber(n: number, index: number) {
        this.numberList[index] = n * 100;
    }
    
    // ChangeDetectionを発火させるようのclickイベント
    public checkChangeDetect() {
        console.log('click event');
    }
}
```

```html:call-function-from-template.component.html
<p>call-function-from-template works!</p>

<div>
    <button (click)="checkChangeDetect()">Button click Event</button>
</div>

<ul>
    <li *ngFor="let number of numberList; let i = index;">
        <div>
            Number: {{number}}, isEven: {{isEven(number)}} <button (click)="changeNumber(number, i)">Change Number</button>
        </div>
    </li>
</ul>
```

こんな感じに表示される。
<img width="1440" alt="スクリーンショット 2020-02-11 16.50.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316318/8424e65d-6e0b-9063-5160-b9ce6987a0d8.png">

## Templateからメソッドを直接呼ぶとChangeDetectionのたびに実行されてしまう
見出しにある通り、なぜTemplateから直接メソッドを呼んでいけないかというとそのメソッドがChangeDetectionのたびに呼ばれてしまうからだ。

試しに「Button click Event」ボタンをクリックしてみると画像のように100個分の要素からメソッドが呼ばれてしまっている。
※なぜ２回呼ばれているのかはわからない…。時間があったら調べてみます。もし知っている方いたらコメントください…。

![call-func.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316318/f290ea44-c9e6-17f3-d907-daa2e45c457d.gif)


## `ChanteDetectionStrategy.OnPush`じゃ防げない？
`NumberList`が`@Input()`を使って親から渡されているかつ、clickイベントが親コンポーネントで実装されていれば、`ChangeDetectionStrategy.OnPush`で防ぐことはできる。が、そのコンポーネント内だと`ChangeDetectionStrategy.OnPush`は効かないため、この設定だけやれば防げるものではない。

## 対策： Pure Pipeを使う
Angularの[Pipes](https://angular.jp/guide/pipes.en#pipes)には **Pure Pipe** と **Impure Pipe** の２つが存在する。ちなみに **デフォルトではPure Pipe** の設定になっている。

```typescript:example.pipe.ts
@Pipe({
  name: 'example',
  pure: true // ←ここで切り替える。デフォルトではtrue
})
```

Pure Pipeというのは、渡された`value`がプリミティブであれば値の変更、オブジェクト(配列含む)であれば新しい参照が与えられたときだけ`transform()`が実行されるPipeのことだ。

まずは与えられた数値が偶数かどうか判定するPipeを作成する。
※実行されたかわかりやすいようにログも混ぜてある。

```typescript:is-even.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
    name: 'isEven',
})
export class IsEvenPipe implements PipeTransform {
    transform(n: number): string {
        console.log(`Pipe Function isEvent Called, Number: ${n}`);
        return n % 2 === 0 ? 'True' : 'False';
    }
}
```

またさっきのテンプレートをPipeを使うように修正する。

```diff:call-function-from-template.component.html
<p>call-function-from-template works!</p>

<div>
    <button (click)="checkChangeDetect()">Button click Event</button>
</div>

<ul>
    <li *ngFor="let number of numberList; let i = index;">
-        <div>
-            Number: {{number}}, isEven: {{isEven(number)}} <button (click)="changeNumber(number, i)">Change Number</button>
-        </div>
+        <div>
+            Number: {{number}}, isEven: {{number | isEven}} <button (click)="changeNumber(number, i)">Change Number</button>
+        </div>
    </li>
</ul>
```

これで実行してみると、ChangeDetection時にPipeの関数が呼び出されず、値が変更されたPipeのみだけ実行されていることが確認できる。
![call-pipe-func.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316318/18acaa16-cbd4-de61-f43e-57d08feadfa0.gif)

## さいごに
ChangeDetectionはいろんなところで出てくるんだなぁ…(雑)

## 参考
[Why you should never use function calls in Angular template expressions](https://medium.com/showpad-engineering/why-you-should-never-use-function-calls-in-angular-template-expressions-e1a50f9c0496)