開発者ドキュメント

ControlValueAccessorを使用してAngularでカスタムフォームコントロールを作成する方法

序章

Angularでフォームを作成する場合、標準のテキスト入力、選択、またはチェックボックスではない入力が必要になることがあります。 ControlValueAccessorインターフェースを実装し、コンポーネントをNG_VALUE_ACCESSORとして登録することで、カスタムフォームコントロールを、ネイティブ入力であるかのように、テンプレート駆動型またはリアクティブフォームにシームレスに統合できます。

この記事では、基本的な星評価入力コンポーネントをControlValueAccessorに変換します。

前提条件

このチュートリアルを完了するには、次のものが必要です。

このチュートリアルは、ノードv16.4.2、npm v7.18.1、angularv12.1.1で検証されました。

ステップ1—プロジェクトの設定

まず、新しいRatingInputComponentを作成します。

これは、@angular/cliで実現できます。

  1. ng generate component rating-input --inline-template --inline-style --skip-tests --flat --prefix

これにより、新しいコンポーネントがアプリdeclarationsに追加され、rating-input.component.tsファイルが生成されます。

src / app /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 {
  }

}

テンプレート、スタイル、およびロジックを追加します。

src / app /rating-input.component.ts
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);
  }
}

コンポーネントのvalue0から5)を取得し、rate関数を呼び出すか、星が欲しい。

コンポーネントをアプリケーションに追加できます。

src / app / app.component.html
<rating-input></rating-input>

そして、アプリケーションを実行します。

  1. ng serve

そして、Webブラウザでそれを操作します。

これは素晴らしいことですが、この入力をフォームに追加して、すべてがまだ機能することを期待することはできません。 ControlValueAccessorにする必要があります。

ステップ2—カスタムフォームコントロールを作成する

RatingInputComponentをネイティブ入力(つまり、真のカスタムフォームコントロール)であるかのように動作させるには、Angularにいくつかの方法を指示する必要があります。

これらの4つは、ControlValueAccessorインターフェイス、つまりフォームコントロールとネイティブ要素またはカスタム入力コンポーネントの間のブリッジを構成します。 コンポーネントがそのインターフェースを実装したら、それをNG_VALUE_ACCESSORとして提供して使用できるようにすることで、Angularにそのことを伝える必要があります。

コードエディタでrating-input.component.tsに再度アクセスし、次の変更を加えます。

src / app /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;
  }
}

このコードを使用すると、入力を無効にすることができます。無効にすると、入力がある程度透過的になります。

アプリケーションを実行します。

  1. ng serve

そして、Webブラウザでそれを操作します。

入力コントロールを無効にすることもできます。

src / app / app.component.html
<rating-input [disabled]="true"></rating-input>

これで、RatingInputComponentはカスタムフォームコンポーネントであると言えます。 テンプレート駆動型またはリアクティブ形式で、他のネイティブ入力と同じように機能します(AngularはそれらにControlValueAccessorsを提供します!)。

結論

この記事では、基本的な星評価入力コンポーネントをControlValueAccessorに変換しました。

あなたは今それに気付くでしょう:

Angularについて詳しく知りたい場合は、Angularトピックページで演習とプログラミングプロジェクトを確認してください。

モバイルバージョンを終了