Angular4-フォームコントロールの値アクセサーなし


146

カスタム要素があります:

<div formControlName="surveyType">
  <div *ngFor="let type of surveyTypes"
       (click)="onSelectType(type)"
       [class.selected]="type === selectedType">
    <md-icon>{{ type.icon }}</md-icon>
    <span>{{ type.description }}</span>
  </div>
</div>

formControlNameを追加しようとすると、エラーメッセージが表示されます。

エラーエラー:名前が 'surveyType'のフォームコントロールに値アクセサーがありません

追加ngDefaultControlに失敗しました。それは入力/選択がないためだと思われます...そして私は何をすべきか分かりません。

私のクリックをこのformControlにバインドして、誰かがカード全体をクリックしたときに私の「タイプ」がformControlにプッシュされるようにします。出来ますか?


私の要点はわかりません:formControlはHTMLのフォームコントロールに移動しますが、divはフォームコントロールではありません。サーベイタイプを私のカードdivのtype.idにバインドします。
jbtd 2017

私は古い角度の方法を使用してselectedTypeをバインドできることを知っていますが、角度4から反応型フォームを使用して学習しようとしており、このタイプのケースでformControlを使用する方法がわかりません。
jbtd 2017

OK、それは多分jsutだと思うので、事後対応型のフォームでは対応できない。とにかくThx :)
jbtd 2017

ここで、巨大なフォームをサブコンポーネントに分解する方法について回答しました。stackoverflow.com/ a / 56375605/2398593ですが、これはカスタムコントロール値アクセサーだけでも非常によく当てはまります。また、チェックアウトgithub.com/cloudnc/ngx-sub-form :)
maxime1992

回答:


250

formControlNameを実装するディレクティブでのみ使用できますControlValueAccessor

インターフェースを実装する

したがって、必要なことを行うには、を実装するコンポーネントを作成する必要がありますControlValueAccessor。つまり、次の3つの関数を実装します

  • writeValue (Angularにモデルからビューに値を書き込む方法を伝える)
  • registerOnChange (ビューが変更されたときに呼び出されるハンドラー関数を登録します)
  • registerOnTouched (コンポーネントがタッチイベントを受け取ったときに呼び出されるハンドラーを登録します。コンポーネントがフォーカスされているかどうかを知るのに役立ちます)。

プロバイダーを登録する

次に、このディレクティブがaであることをAngularに伝える必要がありますControlValueAccessor(TypeScriptがJavaScriptにコンパイルされるときにコードから削除されるため、インターフェイスはそれをカットしません)。これを行うには、プロバイダーを登録します

プロバイダーは、既存の値を提供NG_VALUE_ACCESSORして使用する必要がありますforwardRefここにも必要です。マルチプロバイダーであるNG_VALUE_ACCESSOR必要があります

たとえば、カスタムディレクティブの名前がMyControlComponentの場合、次の行に沿って、@Componentデコレータに渡されるオブジェクト内に何かを追加する必要があります。

providers: [
  { 
    provide: NG_VALUE_ACCESSOR,
    multi: true,
    useExisting: forwardRef(() => MyControlComponent),
  }
]

使用法

コンポーネントを使用する準備が整いました。では、テンプレート駆動型フォームngModelバインディングが正しく動作します。

反応性の形、あなたは今、適切に使用することができますformControlNameし、期待通りのフォームコントロールが動作します。

資源


72

私はあなたが使うべきだと思うformControlName="surveyType"上のinputAにしていませんdiv


必ずはい、私はHTMLフォームコントロールになります他の何かに私のカードのdivをオンにする方法を知らない
jbtd

5
CustomValueAccessorのポイントは、フォームコントロールを

4
@SoEzPzしかし、これは悪いパターンです。ラッパーコンポーネントの入力機能を模倣して、標準のHTMLメソッドを自分で再実装します(したがって、基本的にはホイールを再発明し、コードを冗長にします)。しかし、90%のケース<ng-content>では、ラッパーコンポーネントで使用することで必要なすべてを達成でき、定義する親コンポーネントにformControls<wrapper>内に<input>を置くだけです
Phil

3

エラー手段が、角度が何をすべきかわからないことをあなたは置くときformControldiv。これを修正するには、2つのオプションがあります。

  1. あなたは入れたformControlName箱の角アウトによってサポートされている要素、上。それらは:inputtextareaおよびselectです。
  2. ControlValueAccessorインターフェースを実装します。そうすることで、Angularに「コントロールの値にアクセスする方法」(したがって名前)を伝えます。または簡単な言い方をすると:何をすべきか、formControlName要素にを置くと、それに関連付けられた値が自然にはありません。

さて、ControlValueAccessorインターフェースの実装は、最初は少し困難な場合があります。特に、これに関する優れたドキュメントはあまりなく、コードに多くのボイラープレートを追加する必要があるためです。したがって、これをいくつかの簡単な手順で分解してみましょう。

フォームコントロールを独自のコンポーネントに移動する

を実装するControlValueAccessorには、新しいコンポーネント(またはディレクティブ)を作成する必要があります。フォームコントロールに関連するコードをそこに移動します。このように、それも簡単に再利用できます。既にコンポーネント内にコントロールがあることは、そもそもControlValueAccessorインターフェースを実装する必要がある理由かもしれません。そうでなければ、カスタムコンポーネントをAngularフォームと一緒に使用することができないからです。

コードにボイラープレートを追加する

ControlValueAccessorインターフェースの実装は非常に冗長です。これが付属するボイラープレートです。

import {Component, OnInit, forwardRef} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';


@Component({
  selector: 'app-custom-input',
  templateUrl: './custom-input.component.html',
  styleUrls: ['./custom-input.component.scss'],

  // a) copy paste this providers property (adjust the component name in the forward ref)
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true
    }
  ]
})
// b) Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {

  // c) copy paste this code
  onChange: any = () => {}
  onTouch: any = () => {}
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }

  // d) copy paste this code
  writeValue(input: string) {
    // TODO
  }

では、個々のパーツは何をしているのでしょうか?

  • a)ControlValueAccessorインターフェイスを実装したことをランタイム中にAngularに知らせます
  • b)ControlValueAccessorインターフェースを実装していることを確認します
  • c)これはおそらく最も混乱しやすい部分です。基本的に何をやっていることは、あなたが角度をあなたのクラスのプロパティ/メソッドをオーバーライドするための手段を与えているonChangeonTouch、それはあなたがそれらの機能を呼び出すことができるように、実行時に独自の実装、だと。したがって、この点を理解することが重要です。onChangeとonTouchを自分で実装する必要はありません(最初の空の実装以外)。(c)で行う唯一のことは、Angularに独自の関数をクラスにアタッチさせることです。どうして?そのため、Angularが提供するおよびメソッドを適切なタイミングで呼び出すことができます。以下でこれがどのように機能するかを見ていきます。onChangeonTouch
  • d)またwriteValue、次のセクションで、実装時にメソッドがどのように機能するかを確認します。ここに配置したので、必要なすべてのプロパティControlValueAccessorが実装され、コードは引き続きコンパイルされます。

writeValueを実装する

writeValueが、フォームコントロールが外側で変更されたときに、カスタムコンポーネント内で何か行うことです。したがって、たとえば、カスタムフォームコントロールコンポーネントに名前を付けて、app-custom-inputそれを次のように親コンポーネントで使用しているとします。

<form [formGroup]="form">
  <app-custom-input formControlName="myFormControl"></app-custom-input>
</form>

その後writeValue、親コンポーネントが何らかの方法での値を変更するたびにトリガーされますmyFormControl。これは、たとえば、フォーム(this.form = this.formBuilder.group({myFormControl: ""});)の初期化中またはフォームのリセット時などthis.form.reset();です。

フォームコントロールの値が外側で変化した場合に通常行うことは、フォームコントロールの値を表すローカル変数に書き込むことです。たとえばCustomInputComponent、テキストベースのフォームコントロールを中心とする場合、次のようになります。

writeValue(input: string) {
  this.input = input;
}

のhtmlでCustomInputComponent

<input type="text"
       [ngModel]="input">

Angularのドキュメントで説明されているように、input要素に直接書き込むこともできます。

これで、何かが外部で変更されたときにコンポーネントの内部で何が起こるかを処理しました。次に、別の方向を見てみましょう。コンポーネントの内部で何かが変更されたときに、どのように外部の世界に通知しますか?

onChangeを呼び出す

次のステップは、内の変更について親コンポーネントに通知することCustomInputComponentです。ここで、上記の(c)のonChangeおよびonTouch関数が機能します。これらの関数を呼び出すことで、コンポーネント内部の変更について外部に通知できます。値の変更を外部に伝達するには、新しい値を引数としてonChange呼び出す必要があります。たとえば、ユーザーがinputカスタムコンポーネントのフィールドに何かを入力した場合onChange、更新された値を使用して呼び出します。

<input type="text"
       [ngModel]="input"
       (ngModelChange)="onChange($event)">

上から実装(c)をもう一度確認すると、何が起こっているかがわかります。Angularは、onChangeクラスプロパティへの独自の実装です。この実装では、更新されたコントロール値である1つの引数が必要です。今行っているのは、そのメソッドを呼び出して、Angularに変更について知らせることです。Angularは先に進み、外側のフォーム値を変更します。これは、これらすべての重要な部分です。あなたはAngularにフォームコントロールをいつ更新するべきか、そしてを呼び出すことでどのような値で更新するかを伝えましたonChange。「コントロール値にアクセスする」手段を与えました。

ちなみに名前onChangeは私が選んだものです。ここでは、たとえばpropagateChange、同様のものを選択できます。ただし、どのような名前を付けても、Angularによって提供され、registerOnChange実行時にメソッドによってクラスにバインドされる、1つの引数を取る関数と同じになります。

onTouchを呼び出す

フォームコントロールは「触れる」ことができるため、Angularにカスタムフォームコントロールがいつ触れられるかを理解する手段を与える必要もあります。onTouch関数を呼び出すことで、それを実行できます。したがって、ここでの例で、Angularがすぐに使えるフォームコントロールに対して行っている方法に準拠したいonTouch場合は、入力フィールドがぼやけているときに呼び出す必要があります。

<input type="text"
       [(ngModel)]="input"
       (ngModelChange)="onChange($event)"
       (blur)="onTouch()">

繰り返しますが、onTouch私が選んだ名前ですが、実際の機能はAngularによって提供され、引数はありません。これは、Angularにフォームコントロールが操作されたことを知らせるだけなので、理にかなっています。

すべてを一緒に入れて

それがすべて一緒になったとき、それはどのように見えますか?次のようになります。

// custom-input.component.ts
import {Component, OnInit, forwardRef} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';


@Component({
  selector: 'app-custom-input',
  templateUrl: './custom-input.component.html',
  styleUrls: ['./custom-input.component.scss'],

  // Step 1: copy paste this providers property
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true
    }
  ]
})
// Step 2: Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {

  // Step 3: Copy paste this stuff here
  onChange: any = () => {}
  onTouch: any = () => {}
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }

  // Step 4: Define what should happen in this component, if something changes outside
  input: string;
  writeValue(input: string) {
    this.input = input;
  }

  // Step 5: Handle what should happen on the outside, if something changes on the inside
  // in this simple case, we've handled all of that in the .html
  // a) we've bound to the local variable with ngModel
  // b) we emit to the ouside by calling onChange on ngModelChange

}
// custom-input.component.html
<input type="text"
       [(ngModel)]="input"
       (ngModelChange)="onChange($event)"
       (blur)="onTouch()">
// parent.component.html
<app-custom-input [formControl]="inputTwo"></app-custom-input>

// OR

<form [formGroup]="form" >
  <app-custom-input formControlName="myFormControl"></app-custom-input>
</form>

その他の例

ネストされたフォーム

コントロール値アクセサは、ネストされたフォームグループに適したツールではないことに注意してください。ネストされたフォームグループの場合は、@Input() subform代わりに単に使用できます。コントロールバリューアクセサーは、ラップすることを意図したものcontrolsではなく、groups!ネストされたフォームの入力を使用する方法の例を参照してください:https : //stackblitz.com/edit/angular-nested-forms-input-2

出典


-1

私にとっては、Angularがこのタイプのコントロールに対して異なるValueAccessorを持っているため、選択入力コントロールの「複数」属性が原因でした。

const countryControl = new FormControl();

そしてテンプレートの中でこのように使用します

    <select multiple name="countries" [formControl]="countryControl">
      <option *ngFor="let country of countries" [ngValue]="country">
       {{ country.name }}
      </option>
    </select>

詳細については、公式ドキュメントを参照してください


「複数」によるものですか?私はあなたのコードが何かを解決する方法、または元々の問題は何であるかわかりません。コードは通常の基本的な使用法を示しています。
LazarLjubenović
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.