Angular 表示用の値を別に持つ入力コンポーネントの作成

### 経緯
Angularリアクティブフォームを用いた品質管理アプリを開発しています。
計測値を入力する箇所があり、当初は以下の仕様でした。

- Inputタグを用いる。
- 入力の仕方はInput要素に直接入力ではなく、Input要素タップ時、テンキーダイアログを開き、そこで入力した値を、元のInput要素にセットする。
- 入力値の小数点以下の桁数は定まっていない。小数点以下3桁で入力することもあれば、4桁で入力することもある。

顧客様より、以下の要望をいただきました。

- 表示は小数点以下第3位で丸めた値に統一する。
- 値は入力した通りの値を保存する。小数点以下第4位まで入力したのであれば、その通りの値を保存する。

表示用のinputタグとは別に値保存用のtype="hidden"のinputタグを追加して対応を考えましたが、コンポーネント側も修正が必要となり、考えていたところ、新たに「表示値を別に持つ入力コンポーネント(以下、新コンポーネント)」を作成する方法を思いつき、実装することができました。その方法の要点をまとめます。


### 環境
- Node.js v12.13.0
- Angular 9

### 修正前
元のコードは以下です。

```typescript:app.component.ts
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms'

@Component({
  selector: 'app-root',
  template: `
    <div>
      <label>
        表示値:
        <!--値を表示する部分。クリック時、値を編集部分に送る-->
        <!--※この部分が当記事の修正対象になります-->
        <input type="number" readonly
          [formControl]="displayControl" 
          (click)="editValue(displayControl.value)" 
          #displayControl
        >
      </label>
    </div>
    <hr>
    <div>
      <label>
        編集:
        <input 
          type="number" 
          [formControl]="editControl"
        >
        <button (click)="save()">確定</button>
      </label>
    </div>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  //表示用FormControl
  displayControl = new FormControl(null);
  //編集用FormControl
  editControl = new FormControl(null);

  //編集部分に値をセットする。
  editValue(value: number) {
    this.editControl.setValue(value);
  }
  
  //編集部分で入力された値を保存する。
  save() {
    this.displayControl.setValue(this.editControl.value);
    this.editControl.reset();
  }
}
```

### 表示値と保持値を別に持つコンポーネントの作成
1. Angular CLIを用いてコンポーネントの雛形を作成する。
    以下のコマンドを入力します。

    ```
    npx ng g c display-another-value
    ```

1. コーディング
    まず全容を示します。

    ```typescript:display-anoter-value.ts
    import { Component, OnInit, Input } from '@angular/core';
    import { FormControl } from '@angular/forms';

    @Component({
      selector: 'app-display-another-value',
      template: `
        <input type="hidden" [value]="valueControl.value">
        <input type="number" readonly [value]="getRoundValue()">
      `,
      styleUrls: ['./display-another-value.component.css']
    })
    export class DisplayAnotherValueComponent implements OnInit {
      @Input() valueControl: FormControl;

      constructor() { }

      getRoundValue(): string {
        if (!this.valueControl.value) return null;

        const multipleValue = 1000;

        return (Math.round(this.valueControl.value * multipleValue) / multipleValue).toFixed(3);
      }
      ngOnInit(): void {
      }

    }
    ```
    1. 解説
        1. 親コンポーネントからFormContorlを受け取る

            ```typescript
            @Input() valueControl: FormControl;
            ```
        1. テンプレートに値保持用のinputタグを配置する

            ```html
            <input type="hidden" [value]="valueControl.value">
            ```
            親コンポーネントから受け取ったFormContorlであるvalueControlのvalueをプロパティバインディングを用いて、inputタグのvalueプロパティにバインドします。
        1. テンプレートに表示値用のinputタグを配置する

            ```html
            <input type="number" readonly [value]="getRoundValue()">
            ```
            表示用のinputタグのvalueプロパティには、丸め処理を行った値をバインドします。
        1. 丸め処理
            FormContorl.valueを小数点以下3位で丸める処理です。
            誤差を考慮して、元の値を1000倍し小数点以下を四捨五入し、1000で割り、toFixed関数で小数点以下3桁に整形しています。

            ```typescript
            getRoundValue(): string {
              if (!this.valueControl.value) return null;
              
              //桁数
              const digit = 3;

              return (Math.round(this.valueControl.value * Math.pow(10, digit)) / Math.pow(10, digit)).toFixed(digit);
            }
            ```

### 親コンポーネントの修正
1. 親コンポーネントに新コンポーネントを埋め込む

    ```typescript:app.component.ts
    Component({
      selector: 'app-root',
      template: `
        <div>
          <label>
            表示値:
            <!--新コンポーネントを埋め込む-->
            <app-display-another-value 
              [valueControl]="displayControl" 
              (click)="editValue(displayControl.value)"
            ></app-display-another-value>
          </label>
        </div>
        <hr>
        <div>
          <label>
            編集:<input type="number" [formControl]="editControl"><button (click)="save()">確定</button>
          </label>
        </div>
      `,
      styleUrls: ['./app.component.css']
    })
    export class AppComponent {
      //以下は修正前と変更はありません。
      
      //表示用FormControl
      displayControl = new FormControl(null);
      //編集用FormControl
      editControl = new FormControl(null);

      //編集部分に値をセットする。
      editValue(value: number) {
        this.editControl.setValue(value);
      }

      //編集部分で入力された値を保存する。
      save() {
        this.displayControl.setValue(this.editControl.value);
        this.editControl.reset();
      }
    }
    ```
1. 解説
    新コンポーネントで定義したvalueControlプロパティにdisplayControl(FormControl型)をバインドします。クリック時のイベントは修正前と変更なくイベントバインディングを用いています。

    ```html
    <app-display-another-value 
      [valueControl]="displayControl" 
      (click)="editValue(displayControl.value)"
    ></app-display-another-value>
    ```

以上です。
### 感想
元のコンポーネントで、値保持用のtype="hidden"属性のinputタグを追加する場合だと、その隠しinputに対応したFormControlを設けなければなりませんが、新コンポーネントの方法では、元コンポーネントのコード部分に変更なく実装できましたし、よりAngularらしい方法をとることができたと思います。

### 参考
[Angularアプリケーションプログラミング  山田 祥寛 技術評論社](https://www.amazon.co.jp/Angular%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E5%B1%B1%E7%94%B0-%E7%A5%A5%E5%AF%9B/dp/4774191302)