Angulerで標準のコンポーネントをカスタマイズして自前のフォームを作るやり方です。
AngularConnect2017の動画を公式のドキュメントとして参考にしています。
https://youtu.be/ZNTsdaZiqP8?t=9896
やりたいこと
上記の動画にあわせて、入力必須のテキストフィールドコンポーネントを作って行きます。
- 標準のinputなどのフォームと同様の使い方ができること
- 入力必須のバリデーションが行われること
- 未入力の場合にエラーメッセージを表示されること
コンポーネントのひな型
まずはこれから実装していくコンポーネントの最初の状態です。
import { Component } from '@angular/core';
@Component({
selector: 'app-required-text',
templateUrl: './requiredText.component.html',
styleUrls: [ './requiredText.component.css' ],
})
export class RequiredTextComponent implements ControlValueAccessor {
}
<div>
<input type="text">
</div>
ControlValueAccessor
カスタムコンポーネントを作るにあたり、下の例のようにngModelやFormControlのプロパティを標準のフォームのように使えるようにします。
<custom-input [(ngModel)]="name"></custom-input>
ngModelやFormControlのプロパティからデータをバインドさせるようにするには、「ControlValueAccessor」をimplmentさせます。これは標準のinputやtextareaなどすべてのフォームがimplementしているもので、新しくフォームを作る際にもimplementを行います。
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参照して要素を取得できる状態にします。
<input type="text" #input>
@ViewChild('input') private input: ElementRef;
writeValue(obj: any): void {
this.input.nativeElement.value = obj;
}
registerOnChange
Viewでの変更時のコールバックを登録します。
onChangeのメソッドを定義し、View側で変更が起きたらonChangeを呼ぶようにします。
registerOnChangeでonChangeのからコールバックを呼び出すようにします。
<input type="text" (input)="onChange($event.target.value)" #input>
onChange: (obj: any) => void;
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched
ViewでのTouch(blur)イベントのコールバックを登録します。
registerOnChangeと同様に、onTouchedメソッドを定義しregisterOnTouched内でコールバックを呼び出すようにします。
<input type="text" #input
(input)="onChange($event.target.value)"
(blur)="onTouched()">
onTouched: (obj: any) => void;
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState
フォームの活性/非活性をViewに設定します。
<input type="text" #input
(input)="onChange($event.target.value)"
(blur)="onTouched()"
[disabled]="disabled">
disabled: boolean
setDisabledState(isDisabled: boolean){
this.disabled = isDisabled;
}
Provider
さらにControlValueAccessorをimplementした際には、ValueAccessorをDIする必要があります。
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: CustomRadioFormControlComponent,
multi: true,
},
]
ここまでのDEMO
自作したapp-required-textにngModelを設定し、2wayバインドができていることが確認できます。
もちろんReactiveFormにしてFormControlを渡して使用することができます。
<app-required-text [(ngModel)]="name"></app-required-text>
{{name}}
次回に続きます。