AngularのngDefaultControlとは何ですか?


100

いいえ、これは重複した質問ではありません。ご覧のとおり、SOとGithubには、[(ngModel)]ディレクティブがあり、フォームに含まれていないタグにこのディレクティブを追加することを規定する多くの質問と問題があります。追加しないとエラーが発生します。

ERROR Error: No value accessor for form control with unspecified name attribute

OK、この属性をそこに置くとエラーはなくなります。ちょっと待って!それが何をするか誰も知らない!そして、Angularのドキュメントはそれについてまったく触れていません。値アクセサーが必要ないことがわかっているのに、なぜそれが必要なのですか?この属性は値アクセサーにどのように関連付けられていますか?この指令は何をしますか?バリューアセッサーとは何ですか。どのように使用しますか?

そして、なぜ誰もがすべてを理解していないことをし続けるのですか?このコード行を追加するだけで機能します。ありがとうございます。これは優れたプログラムを作成する方法ではありません。

その後。私はAngularのフォームに関する1つではなく2つの巨大なガイドと次のセクションを読みましたngModel

そして、あなたは何を知っていますか?値アクセサまたはのいずれかについての単一の言及ではありませんngDefaultControl。それはどこにある?

回答:


178

[ngDefaultControl]

サードパーティのコントロールは、ControlValueAccessor角度のあるフォームで機能する必要があります。Polymerのようなそれらの多くは<paper-input><input>ネイティブ要素のように動作するため、を使用できますDefaultValueAccessorngDefaultControl属性を追加すると、そのディレクティブを使用できるようになります。

<paper-input ngDefaultControl [(ngModel)]="value>

または

<paper-input ngDefaultControl formControlName="name">

これが、この属性が導入された主な理由です。

angular2のアルファ版ではng-default-control属性と呼ばれていました

ですからngDefaultControl用のセレクタの一つであるDefaultValueAccessorのディレクティブは:

@Directive({
  selector:
      'input:not([type=checkbox])[formControlName],
       textarea[formControlName],
       input:not([type=checkbox])[formControl],
       textarea[formControl],
       input:not([type=checkbox])[ngModel],
       textarea[ngModel],
       [ngDefaultControl]', <------------------------------- this selector
  ...
})
export class DefaultValueAccessor implements ControlValueAccessor {

どういう意味ですか?

これは、独自の値アクセサーを持たない要素(ポリマーコンポーネントなど)にこの属性を適用できることを意味します。したがって、この要素はから振る舞いをしDefaultValueAccessor、この要素を角度付きフォームで使用できます。

それ以外の場合は、独自の実装を提供する必要があります ControlValueAccessor

ControlValueAccessor

Angular docsの状態

ControlValueAccessorは、AngularフォームAPIとDOMのネイティブ要素の間のブリッジとして機能します。

シンプルなangular2アプリケーションで次のテンプレートを書いてみましょう:

<input type="text" [(ngModel)]="userName">

input上記の動作を理解するには、この要素に適用されるディレクティブを知る必要があります。ここでangularはエラーに関するいくつかのヒントを与えます:

未処理のPromise拒否:テンプレート解析エラー:「入力」の既知のプロパティではないため、「ngModel」にバインドできません。

さて、私たちはSOを開いて答えを得ることができFormsModuleます@NgModule:あなたのインポート:

@NgModule({
  imports: [
    ...,
    FormsModule
  ]
})
export AppModule {}

私たちはそれをインポートし、すべて意図したとおりに機能します。しかし、内部では何が起こっているのでしょうか?

FormsModuleは、次のディレクティブをエクスポートします。

@NgModule({
 ...
  exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
})
export class FormsModule {}

ここに画像の説明を入力してください

調査の結果、3つの指令が私たちに適用されることがわかりました。 input

1)NgControlStatus

@Directive({
  selector: '[formControlName],[ngModel],[formControl]',
  ...
})
export class NgControlStatus extends AbstractControlStatus {
  ...
}

2)NgModel

@Directive({
  selector: '[ngModel]:not([formControlName]):not([formControl])',
  providers: [formControlBinding],
  exportAs: 'ngModel'
})
export class NgModel extends NgControl implements OnChanges, 

3)DEFAULT_VALUE_ACCESSOR

@Directive({
  selector:
      `input:not([type=checkbox])[formControlName],
       textarea[formControlName],
       input:not([type=checkbox])formControl],
       textarea[formControl],
       input:not([type=checkbox])[ngModel],
       textarea[ngModel],[ngDefaultControl]',
  ,,,
})
export class DefaultValueAccessor implements ControlValueAccessor {

NgControlStatusディレクティブは、、、のようなクラスを操作するだけng-validでありng-touchedng-dirtyここでは省略できます。


DefaultValueAccesstor提供NG_VALUE_ACCESSORプロバイダの配列にトークン:

export const DEFAULT_VALUE_ACCESSOR: any = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => DefaultValueAccessor),
  multi: true
};
...
@Directive({
  ...
  providers: [DEFAULT_VALUE_ACCESSOR]
})
export class DefaultValueAccessor implements ControlValueAccessor {

NgModelディレクティブNG_VALUE_ACCESSORは、同じホスト要素で宣言されたコンストラクタトークンを挿入します。

export NgModel extends NgControl implements OnChanges, OnDestroy {
 constructor(...
  @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {

私たちの場合NgModelは注入されDefaultValueAccessorます。そしてNgModelディレクティブは共有setUpControl関数を呼び出します:

export function setUpControl(control: FormControl, dir: NgControl): void {
  if (!control) _throwError(dir, 'Cannot find control with');
  if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with');

  control.validator = Validators.compose([control.validator !, dir.validator]);
  control.asyncValidator = Validators.composeAsync([control.asyncValidator !, dir.asyncValidator]);
  dir.valueAccessor !.writeValue(control.value);

  setUpViewChangePipeline(control, dir);
  setUpModelChangePipeline(control, dir);

  ...
}

function setUpViewChangePipeline(control: FormControl, dir: NgControl): void 
{
  dir.valueAccessor !.registerOnChange((newValue: any) => {
    control._pendingValue = newValue;
    control._pendingDirty = true;

    if (control.updateOn === 'change') updateControl(control, dir);
  });
}

function setUpModelChangePipeline(control: FormControl, dir: NgControl): void {
  control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
    // control -> view
    dir.valueAccessor !.writeValue(newValue);

    // control -> ngModel
    if (emitModelEvent) dir.viewToModelUpdate(newValue);
  });
}

そして、これが実際の橋です:

ここに画像の説明を入力してください

NgModelコントロール(1)をセットアップし、dir.valueAccessor !.registerOnChangeメソッドを呼び出します。(2)プロパティにControlValueAccessorコールバックを格納し、イベントが発生したときにこのコールバックを起動します(3)。そして最後に、コールバック内で関数が呼び出されます(4)onChangeinputupdateControl

function updateControl(control: FormControl, dir: NgControl): void {
  dir.viewToModelUpdate(control._pendingValue);
  if (control._pendingDirty) control.markAsDirty();
  control.setValue(control._pendingValue, {emitModelToViewChange: false});
}

ここで、角度呼び出しはAPIを形成しますcontrol.setValue

それはそれがどのように機能するかの短いバージョンです。


私は双方向のバインディングを作成@Input() ngModel@Output() ngModelChangeましたが、これで十分です。これはまったく同じように同じことをしているように見えます。たぶん私は私のフィールドに名前を付けるべきではありませんngModelか?
ガーマン2017

2
このコンポーネントを角張ったフォームで使用しない場合は、独自の双方向バインディングを作成して@Input() value; @Output() valueChange: EventEmitter<any> = new EventEmitter();、次のように使用できます[(value)]="someProp"
yurzui

1
それがまさに私がやっていたことです。しかし、私は私の「値」に名前を付け、ngModelAngularは私にエラーを投げ始め、ControlValueAccessorを要求し始めました。
ガーマン2017

vueとReactのngDefaultControlに相当する人はいますか?つまり、コントロール値アクセサーを使用してカスタム入力コンポーネントを角度で作成し、それをAngular要素のWebコンポーネントとしてラップしました。同じプロジェクトで、ngDefaultControlを使用して角度のあるフォームで機能するようにする必要がありました。しかし、それらをVue and Reactで機能させるにはどうすればよいですか?ネイティブJSでも?
Kavinda Jayakody

カスタムコンポーネントでngDefaultControlを使用していますが、1つの問題に悩んでいます。formBuilderビュー(カスタム入力コンポーネント)内のformControlにデフォルト値を設定すると、モデルのみが更新されません。何が悪いのですか?
IgorJanković19年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.