Angularでカスタムフォーム(CustomFormControls)を作る(1/2)

Angulerで標準のコンポーネントをカスタマイズして自前のフォームを作るやり方です。

AngularConnect2017の動画を公式のドキュメントとして参考にしています。
https://youtu.be/ZNTsdaZiqP8?t=9896

## やりたいこと
上記の動画にあわせて、入力必須のテキストフィールドコンポーネントを作って行きます。

- 標準のinputなどのフォームと同様の使い方ができること
- 入力必須のバリデーションが行われること
- 未入力の場合にエラーメッセージを表示されること

<img width="204" alt="スクリーンショット 2018-07-11 11.45.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/36813/8220bb10-a335-7763-d4c1-feda07aa3aff.png">

## コンポーネントのひな型
まずはこれから実装していくコンポーネントの最初の状態です。

```requiredText.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-required-text',
  templateUrl: './requiredText.component.html',
  styleUrls: [ './requiredText.component.css' ],
})
export class RequiredTextComponent implements ControlValueAccessor {  
}
```
```requiredText.component.html
<div>
  <input type="text">
</div>
```
## ControlValueAccessor

カスタムコンポーネントを作るにあたり、下の例のようにngModelやFormControlのプロパティを標準のフォームのように使えるようにします。

```
<custom-input [(ngModel)]="name"></custom-input>
```

ngModelやFormControlのプロパティからデータをバインドさせるようにするには、「ControlValueAccessor」をimplmentさせます。これは標準のinputやtextareaなどすべてのフォームがimplementしているもので、新しくフォームを作る際にもimplementを行います。

```requiredText.component.ts
export class RequiredTextComponent implements ControlValueAccessor 
```

## Interface Methodの実装
ControlValueAccessorをimplemntを行ったので、インターフェースで定義されている以下のメソッドを実装します。
  
- writeValue(obj: any): void {}
- registerOnChange(fn: any): void {}
- registerOnTouched(fn: any): void {}
- (Optional) setDisabledState(isDisabled: boolean){}

### writeValue
モデルの値をViewのフォームに設定します。
まず、Viewのフォームを取得するためにDOM側に#でIDを設定します。
モデル側ではViewChildでID参照して要素を取得できる状態にします。

``` requiredText.component.html
<input type="text" #input>
```

``` requiredText.component.ts
@ViewChild('input') private input: ElementRef;
  
writeValue(obj: any): void {
  this.input.nativeElement.value = obj;
}
```
### registerOnChange
Viewでの変更時のコールバックを登録します。
onChangeのメソッドを定義し、View側で変更が起きたらonChangeを呼ぶようにします。
registerOnChangeでonChangeのからコールバックを呼び出すようにします。

``` requiredText.component.html
<input type="text" (input)="onChange($event.target.value)" #input>
```

``` requiredText.component.ts
onChange: (obj: any) => void;

registerOnChange(fn: any): void {
  this.onChange = fn;
}
```

### registerOnTouched
ViewでのTouch(blur)イベントのコールバックを登録します。
registerOnChangeと同様に、onTouchedメソッドを定義しregisterOnTouched内でコールバックを呼び出すようにします。

``` requiredText.component.html
<input type="text" #input 
 (input)="onChange($event.target.value)" 
 (blur)="onTouched()">

```

``` requiredText.component.ts
onTouched: (obj: any) => void;

registerOnTouched(fn: any): void {
  this.onTouched = fn;
}
```

### setDisabledState
フォームの活性/非活性をViewに設定します。

``` requiredText.component.html
<input type="text" #input 
 (input)="onChange($event.target.value)" 
 (blur)="onTouched()" 
 [disabled]="disabled">
```
``` requiredText.component.ts
disabled: boolean

setDisabledState(isDisabled: boolean){
 this.disabled = isDisabled;
}
```

## Provider
さらにControlValueAccessorをimplementした際には、ValueAccessorをDIする必要があります。

```
providers: [
   {
      provide: NG_VALUE_ACCESSOR,
      useExisting: CustomRadioFormControlComponent,
      multi: true,
    },
  ]
```

## ここまでのDEMO
https://stackblitz.com/edit/angular-custom-form-control-k

自作したapp-required-textにngModelを設定し、2wayバインドができていることが確認できます。
もちろんReactiveFormにしてFormControlを渡して使用することができます。

``` app.component.html
<app-required-text [(ngModel)]="name"></app-required-text>
  {{name}}
```
<img width="195" alt="スクリーンショット 2018-07-10 14.54.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/36813/1857c987-66cf-0efb-8399-142447bf8d7a.png">

[次回](https://qiita.com/kiita312/items/c0324db9120f161adc5b)に続きます。

## 参考
- [ryotah’s blog AngularのForm（応用)](http://ryotah.hatenablog.com/entry/2017/11/30/174928)
- https://stackoverflow.com/questions/39661430/angular-2-formcontrolname-inside-component