コントロールのカスタムコントロールを作成する


コントロールの角でカスタムフォームコントロールを作成する方法カスタムフォームコンポーネントを作成することができます.
だから、私はカスタムフォームコントロールを言うとき、私は入力フィールド、ラジオボタン、またはチェックボックスのような典型的なコントロールではないそれらの要素について話しています.例えば、スターレーティングコンポーネントまたはノブ.これらは箱から手に入らない.
また、カスタムフォームコントロールとして使用できる子コンポーネントとしてフォームのセクションを作成することもできます.このように、より大きいフォームは管理可能な部分に分解されることができます.

入力フィールド、ラジオボタン、チェックボックス、ドロップダウンなどの既定のフォームコントロールのすべてについては、カスタムコントロール値アクセサーが既に書き込まれ、角度で出荷されます.EG :CheckboxControlValueAccessor
もっと話をしますControlValueAccessor それは本当にクールなフォームを作成するために使用する方法.

カスタムフォーム要素


私たちが用語フォームを聞くとき、我々はいくつかの入力テキストフィールドと多分若干のチェックボックスとものを考えています.しかし、それは本当に複雑なフォームには、カスタムボタン、リスト、および選択の多くがあるとき、全体のフォームは非常に複雑になる.そして、そのような複雑な形を管理することは、問題であるでしょう.
カスタムフォーム要素がたくさんあるか、フォームが大きくなったとき、より小さなセクションにそれを壊すのはたぶん良い考えです.単一のテンプレートにすべてを配置すると、それは本当に乱雑になります.
フォームを複数のコンポーネントに分割し、メインフォームに接続できます.

カスタムフォームコントロール


ControlValueAccessor 角で来る何かです.これは、DOM要素と角度フォームAPIの間のブリッジとして機能します.
したがって、フォームに接続したいカスタム要素がある場合は、ControlValueAccessorを使用して、要素を角度フォームAPIと互換性のあるものにする必要があります.そうすることで、要素をngModel (テンプレート駆動フォーム)formControl (反応形).
カスタムフォームコントロールの作成方法を見てみましょう.
私が角で始めたとき、私はこのようなものが存在したことに気づいていませんでした.私はフォームの子供のコンポーネントを書いたときに使用し、覚えて@Input() and @Output() フォームのフォームコンポーネントにフォームの値を受け取り、送信するには.私は、子コンポーネントの変更を聞いて、それから親に値を放出しました.
親では、値を取得し、フォームをパッチするために使用します.これは不思議なControlValueAccessorに遭遇するまででした.これ以上の入力と出力、すべてはちょうど動作します.

ControlValueAccessorインターフェイスを実装します。


手順1は、カスタムコンポーネント内のインターフェイスを実装することです.インターフェイスは、クラスにいくつかのメソッドを追加するようお願いします.
interface ControlValueAccessor {
  writeValue(obj: any): void
  registerOnChange(fn: any): void
  registerOnTouched(fn: any): void
  setDisabledState(isDisabled: boolean)?: void
}
それぞれの方法が何をしているか見てみましょう.物事がどうなっているかはっきりしたら、実装に飛び込むことができます.
  • writeValue() - この関数は、要素の値を更新するフォームAPIで呼び出されます.時ngModel or formControl 値の変更、この関数は呼び出され、関数への引数として最新の値が渡されます.最新の値を使用して、コンポーネントの変更を行うことができます.( ref )
  • registerOnChange() - ローカル変数に保存できる引数の関数へのアクセスを取得します.この関数は、カスタムフォームコントロールの値に変更があったときにコールできます.( ref )
  • registerOnTouched() - フォームの状態を更新するために使用できる別の関数へのアクセスを取得しますtouched . したがって、ユーザーがカスタムフォーム要素と対話するときに、保存された関数を呼び出して、要素が相互に作用していることを角度で知ることができます.( ref )
  • setDisabledState() - この関数は、無効な状態が変更されたときにフォームAPIによって呼び出されます.現在の状態を取得し、カスタムフォームコントロールの状態を更新できます.( ref )
  • これらの関数を実装すると、次のステップはNG_VALUE_ACCESSOR 次のようにコンポーネントのプロバイダー配列でトークンを取得します.
    const COUNTRY_CONTROL_VALUE_ACCESSOR: Provider = {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomFormControlComponent),
      multi: true,
    };
    
    @Component({
      selector: 'app-country-selector',
      template: ``,
      providers: [COUNTRY_CONTROL_VALUE_ACCESSOR], // <-- provided here
    })
    export class CustomFormControlComponent implements ControlValueAccessor {}
    
    注意:プロバイダ定数を作成し、providers . また、あなたの使用を見ることができますforwardRef ( ref ) はい.それは私たちがCountrySelectorComponent 参照前に定義されていないクラスです.
    したがって、これらの関数がどのように機能しているかを知ることで、カスタムフォーム要素の実装を開始できます.

    基本形態


    我々は、我々が働くつもりであるベースフォームを見に行くつもりです.基本的な入力フィールドと2つのカスタムフォーム要素があります.
    {
        name: 'Adithya',
        github: 'https://github.com/AdiSreyaj',
        website: 'https://adi.so',
        server: 'IN',
        communications: [{
              label: 'Marketing',
              modes: [{
                  name: 'Email',
                  enabled: true,
                },
                {
                  name: 'SMS',
                  enabled: false,
                }],
            },
            {
              label: 'Product Updates',
              modes: [{
                  name: 'Email',
                  enabled: true,
                },
                {
                  name: 'SMS',
                  enabled: true,
                }],
            },
          ]
      }
    
    これは、我々がデータを必要とする方法です.ヒアservercommunications フィールドはカスタムフォームコントロールに接続されます.私たちはReactive Forms 例では.
    以下にフォームの形式を示します.
    const form = this.fb.group({
        name: [''],
        github: [''],
        website: [''],
        server: [''],
        communications: [[]]
      });
    
    テンプレートで
    <form [formGroup]="form">
        <div class="form-group">
          <label for="name">Name</label>
          <input type="text" id="name" formControlName="name">
        </div>
        <div class="form-group">
          <label for="github">Github</label>
          <input type="url" id="github" formControlName="github">
        </div>
        <div class="form-group">
          <label for="website">Website</label>
          <input type="url" id="website" formControlName="website">
        </div>
        <div class="form-group">
          <label>Region</label>
          <app-country-selector formControlName="server"></app-country-selector>
        </div>
        <div class="form-group">
          <label>Communication</label>
          <app-communication-preference formControlName="communications"></app-communication-preference>
        </div>
      </form>
    
    上記のテンプレートでの注意formControlNameapp-country-selector and app-communication-preference コンポーネント.これらのコンポーネントが実装されている場合にのみ可能ですControlValueAccessor インターフェイス.これは、コンポーネントをフォームコントロールのように動作させる方法です.

    カスタムフォームコントロール


    フォームに直接接続できるカスタムフォームコントロールとして、クールカントリーセレクタコンポーネントを実装する方法を見ていきます.この例では、反応形式を使用します.

    コンポーネントは非常に簡単です、我々はユーザーが与えられたリストから1つの国を選択するようになります.動作はラジオボタンに似ています.唯一の違いは、このデザインを実装するために独自のカスタムコンポーネントを使用していることです.
    いつものように、私たちの国セレクタフォームコントロールの新しいモジュールとコンポーネントを作成することから始めます.
    ここでは、国のセレクタコンポーネントのControlValueAccessorを実装する方法を示します.
    const COUNTRY_CONTROL_VALUE_ACCESSOR: Provider = {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CountrySelectorComponent),
      multi: true,
    };
    
    我々は、内部のプロバイダ配列でそれを提供します@Component デコレータ.
    @Component({
      selector: 'app-country-selector',
      template: `
        <div>
          <ng-container *ngFor="let country of countries">
            <button [disabled]="disabled" (click)="selectCountry(country.code)"
                 [class.selected]="!disabled && selected === country.code">
              <ng-container *ngIf="!disabled && selected === country.code">
                <!-- Checkmark Icon -->
              </ng-container>
              <img [src]="...flag src" [alt]="country.name" />
              <p>{{ country?.name }}</p>
            </button>
          </ng-container>
        </div>
      `,
      providers: [COUNTRY_CONTROL_VALUE_ACCESSOR],
    })
    export class CountrySelectorComponent implements ControlValueAccessor {
      countries = [
        { code: 'IN', name: 'India' },
        { code: 'US', name: 'United States' },
        { code: 'GB-ENG', name: 'England' },
        { code: 'NL', name: 'Netherlands' },
      ];
      selected!: string;
      disabled = false;
      private onTouched!: Function;
      private onChanged!: Function;
    
      selectCountry(code: string) {
        this.onTouched(); // <-- mark as touched
        this.selected = code;
        this.onChanged(code); // <-- call function to let know of a change
      }
    
      writeValue(value: string): void {
        this.selected = value ?? 'IN';
      }
      registerOnChange(fn: any): void {
        this.onChanged = fn; // <-- save the function
      }
      registerOnTouched(fn: any): void {
        this.onTouched = fn; // <-- save the function
      }
      setDisabledState(isDisabled: boolean) {
        this.disabled = isDisabled;
      }
    }
    
    を返しますserver フォームでは、初期値を取得しますwriteValue() メソッド.値を取得し、ローカル変数に割り当てますselected これは状態を管理します.
    ユーザーが別の国をクリックすると、フィールドとしてマークtouched そして、値をselected 変数.主な部分は、私たちもonChanged メソッドを選択し、新しく選択した国コードを渡します.新しい値をフォームコントロールの値として設定します.
    引数をsetDisabledState() 方法は、我々のコンポーネントのために無効状態を実装することができます.したがって、以下のようにフォームから無効にします.
    this.form.get('server').disable();
    
    上記のようにすることは、呼び出しをトリガーしますsetDisabledState() 国のあり方isDisabled が渡され、ローカル変数disabled . これで、このローカル変数を使用してクラスを追加したり、ボタンを無効にできます.
    setDisabledState(isDisabled: boolean) {
        this.disabled = isDisabled;
      }
    
    それはすべてです!カスタムフォームコントロールを正常に作成しました.ギタブレポをチェックしてくださいfull code .

    コミュニケーション環境設定


    ここで、ユーザーが自分の通信環境設定を選択できるように、フォームで2番目のカスタムフォームコントロールを実装する方法を見てみましょう.

    これはまた、チェックボックスの束を持つ非常に単純なコンポーネントです.フォームが初期化された場合、同じ親コンポーネントに追加できます.しかし、別のコンポーネントを作成することによって、我々はそれをより保守的にしています.
    const COM_PREFERENCE_CONTROL_VALUE_ACCESSOR: Provider = {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CommunicationPreferenceComponent),
      multi: true,
    };
    
    @Component({
      selector: 'app-communication-preference',
      template: `<div>
        <ul>
          <ng-container *ngFor="let item of options; index as i">
            <li>
              <p>{{ item?.label }}</p>
              <div>
                <ng-container *ngFor="let mode of item.modes; index as j">
                  <div>
                    <input
                      type="checkbox"
                      [id]="item.label + mode.name"
                      [(ngModel)]="mode.enabled"
                      (ngModelChange)="handleChange(i, j, $event)" />
                    <label [for]="item.label + mode.name">{{ mode.name }}</label>
                  </div>
                </ng-container>
              </div>
            </li>
          </ng-container>
        </ul>
      </div>`,
      providers: [COM_PREFERENCE_CONTROL_VALUE_ACCESSOR],
    })
    export class CommunicationPreferenceComponent implements ControlValueAccessor {
      options: CommunicationPreference[] = [];
      private onTouched!: Function;
      private onChanged!: Function;
      handleChange(itemIndex: number, modeIndex: number, change: any) {
        this.onTouched();
        this.options[itemIndex].modes[modeIndex].enabled = change;
        this.onChanged(this.options);
      }
    
      writeValue(value: any): void {
        this.options = value;
      }
      registerOnChange(fn: any): void {
        this.onChanged = fn;
      }
      registerOnTouched(fn: any): void {
        this.onTouched = fn;
      }
    }
    
    再び、それは我々がしている同じことですoptions コンポーネントのローカル状態を管理する変数.フォームによってトリガされた値の変更がある場合は、writeValue メソッドは、変更された値を持つローカル状態を更新します.
    ユーザーが変更を行うと、ローカル状態を更新し、onChanged メソッドを更新し、フォームを更新する状態を渡します.
    を見つけるcomplete code repoのコンポーネントについて.

    最後の思考


    角度を使用すると、カスタムフォームコントロールを実装するのは本当に簡単になりますControlValueAccessor . いくつかのメソッドを実装することによって、コンポーネントをReactive or Template Driven 簡単にフォーム.
    我々は、すべての種類の狂ったフォーム要素を書くことができますし、親と子の間の通信を処理するロジックを記述せずにそれらを使用します.フォームAPIは私たちのための魔法をしましょう.
    また、このアプローチを使用してフォームのセクションを自分の個々のコンポーネントに分割できます.フォームが大きい/複雑であるならば、私たちは、それから簡単に管理されることができるより小さな構成要素に、それから壊れることができます.

    コードとデモ


  • ギタブhttps://github.com/adisreyaj/ng-custom-form-elements

  • デモ:https://ng-custom-form-elements.vercel.app/
  • 接続する


  • Github
  • コメントセクションであなたの考えを加えてください.
    安全である❤️