ControlValueAccessorを使用してAngularでカスタムフォームコントロールを作成する方法
序章
Angularでフォームを作成する場合、標準のテキスト入力、選択、またはチェックボックスではない入力が必要になることがあります。 ControlValueAccessor
インターフェースを実装し、コンポーネントをNG_VALUE_ACCESSOR
として登録することで、カスタムフォームコントロールを、ネイティブ入力であるかのように、テンプレート駆動型またはリアクティブフォームにシームレスに統合できます。
この記事では、基本的な星評価入力コンポーネントをControlValueAccessor
に変換します。
前提条件
このチュートリアルを完了するには、次のものが必要です。
- Node.jsはローカルにインストールされます。これは、Node.jsのインストール方法とローカル開発環境の作成に従って実行できます。
- AngularプロジェクトのセットアップおよびAngularコンポーネントの使用にある程度精通していると有益な場合があります。
このチュートリアルは、ノードv16.4.2、npm
v7.18.1、angular
v12.1.1で検証されました。
ステップ1—プロジェクトの設定
まず、新しいRatingInputComponent
を作成します。
これは、@angular/cli
で実現できます。
- ng generate component rating-input --inline-template --inline-style --skip-tests --flat --prefix
これにより、新しいコンポーネントがアプリdeclarations
に追加され、rating-input.component.ts
ファイルが生成されます。
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'rating-input',
template: `
<p>
rating-input works!
</p>
`,
styles: [
]
})
export class RatingInputComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
テンプレート、スタイル、およびロジックを追加します。
import { Component } from '@angular/core';
@Component({
selector: 'rating-input',
template: `
<span
<^>*ngFor="let starred of stars; let i = index"
(click)="rate(i + (starred ? (value > i + 1 ? 1 : 0) : 1))"<^>
>
<ng-container *ngIf="starred; else noStar">⭐</ng-container>
<ng-template #noStar>·</ng-template>
</span>
`,
styles: [`
span {
display: inline-block;
width: 25px;
line-height: 25px;
text-align: center;
cursor: pointer;
}
`]
})
export class RatingInputComponent {
stars: boolean[] = Array(5).fill(false);
get value(): number {
return this.stars.reduce((total, starred) => {
return total + (starred ? 1 : 0);
}, 0);
}
rate(rating: number) {
this.stars = this.stars.map((_, i) => rating > i);
}
}
コンポーネントのvalue
(0
から5
)を取得し、rate
関数を呼び出すか、星が欲しい。
コンポーネントをアプリケーションに追加できます。
<rating-input></rating-input>
そして、アプリケーションを実行します。
- ng serve
そして、Webブラウザでそれを操作します。
これは素晴らしいことですが、この入力をフォームに追加して、すべてがまだ機能することを期待することはできません。 ControlValueAccessor
にする必要があります。
ステップ2—カスタムフォームコントロールを作成する
RatingInputComponent
をネイティブ入力(つまり、真のカスタムフォームコントロール)であるかのように動作させるには、Angularにいくつかの方法を指示する必要があります。
- 入力に値を書き込みます-
writeValue
- 入力の値が変更されたときにAngularに通知する関数を登録します-
registerOnChange
- 入力がタッチされたときにAngularに通知する関数を登録します-
registerOnTouched
- 入力を無効にする-
setDisabledState
これらの4つは、ControlValueAccessor
インターフェイス、つまりフォームコントロールとネイティブ要素またはカスタム入力コンポーネントの間のブリッジを構成します。 コンポーネントがそのインターフェースを実装したら、それをNG_VALUE_ACCESSOR
として提供して使用できるようにすることで、Angularにそのことを伝える必要があります。
コードエディタでrating-input.component.ts
に再度アクセスし、次の変更を加えます。
import { Component, forwardRef, HostBinding, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'rating-input',
template: `
<span
*ngFor="let starred of stars; let i = index"
(click)="onTouched(); rate(i + (starred ? (value > i + 1 ? 1 : 0) : 1))"
>
<ng-container *ngIf="starred; else noStar">⭐</ng-container>
<ng-template #noStar>·</ng-template>
</span>
`,
styles: [`
span {
display: inline-block;
width: 25px;
line-height: 25px;
text-align: center;
cursor: pointer;
}
`],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RatingInputComponent),
multi: true
}
]
})
export class RatingInputComponent implements ControlValueAccessor {
stars: boolean[] = Array(5).fill(false);
// Allow the input to be disabled, and when it is make it somewhat transparent.
@Input() disabled = false;
@HostBinding('style.opacity')
get opacity() {
return this.disabled ? 0.25 : 1;
}
// Function to call when the rating changes.
onChange = (rating: number) => {};
// Function to call when the input is touched (when a star is clicked).
onTouched = () => {};
get value(): number {
return this.stars.reduce((total, starred) => {
return total + (starred ? 1 : 0);
}, 0);
}
rate(rating: number) {
if (!this.disabled) {
this.writeValue(rating);
}
}
// Allows Angular to update the model (rating).
// Update the model and changes needed for the view here.
writeValue(rating: number): void {
this.stars = this.stars.map((_, i) => rating > i);
this.onChange(this.value);
}
// Allows Angular to register a function to call when the model (rating) changes.
// Save the function as a property to call later here.
registerOnChange(fn: (rating: number) => void): void {
this.onChange = fn;
}
// Allows Angular to register a function to call when the input has been touched.
// Save the function as a property to call later here.
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
// Allows Angular to disable the input.
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
}
このコードを使用すると、入力を無効にすることができます。無効にすると、入力がある程度透過的になります。
アプリケーションを実行します。
- ng serve
そして、Webブラウザでそれを操作します。
入力コントロールを無効にすることもできます。
<rating-input [disabled]="true"></rating-input>
これで、RatingInputComponent
はカスタムフォームコンポーネントであると言えます。 テンプレート駆動型またはリアクティブ形式で、他のネイティブ入力と同じように機能します(AngularはそれらにControlValueAccessors
を提供します!)。
結論
この記事では、基本的な星評価入力コンポーネントをControlValueAccessor
に変換しました。
あなたは今それに気付くでしょう:
Angularについて詳しく知りたい場合は、Angularトピックページで演習とプログラミングプロジェクトを確認してください。