17
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-07-11

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

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

やりたいこと

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

  • 標準のinputなどのフォームと同様の使い方ができること
  • 入力必須のバリデーションが行われること
  • 未入力の場合にエラーメッセージを表示されること
スクリーンショット 2018-07-11 11.45.41.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

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

app.component.html
<app-required-text [(ngModel)]="name"></app-required-text>
  {{name}}
スクリーンショット 2018-07-10 14.54.34.png

次回に続きます。

参考

17
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?