いいえ、これは重複した質問ではありません。SO や Github には、このディレクティブを[(ngModel)]
、フォームに含まれていないディレクティブを持つタグに追加するように指示する質問や問題が山ほどあります。追加しないと、エラーが発生します。
ERROR Error: No value accessor for form control with unspecified name attribute
わかりました。この属性をそこに置けばエラーはなくなります。でも、待ってください。誰もそれが何をするのか知りません。そして、Angular のドキュメントにはそれについてまったく触れられていません。必要がないとわかっているのに、なぜ値アクセサーが必要なのでしょうか? この属性は値アクセサーとどのように関連しているのでしょうか? このディレクティブは何をしますか? 値アクセサーとは何ですか? どのように使用しますか?
そして、なぜ誰もがまったく理解していないことを続けるのでしょうか? このコード行を追加するだけで動作します。ありがとうございます。これは良いプログラムを書く方法ではありません。
そして、Angular のフォームに関する1 つではなく2 つの大きなガイドと、次のセクションを読みましたngModel
。
- https://angular.io/guide/forms
- https://angular.io/guide/reactive-forms
- https://angular.io/guide/template-syntax#ngModel
ところで、値アクセサや については何も言及されていませんngDefaultControl
。どこにあるのでしょうか?
ベストアンサー1
[ngデフォルトコントロール]
サードパーティのコントロールでは、ControlValueAccessor
Angular フォームで機能するために が必要です。Polymer の など、多くのコントロールはネイティブ要素<paper-input>
のように動作するため<input>
、 を使用できますDefaultValueAccessor
。ngDefaultControl
属性を追加すると、そのディレクティブを使用できるようになります。
<paper-input ngDefaultControl [(ngModel)]="value>
または
<paper-input ngDefaultControl formControlName="name">
これがこの属性が導入された主な理由です。
これは、angular2 のアルファ バージョンではng-default-control
属性と呼ばれていました。
DefaultValueAccessorngDefaultControl
ディレクティブのセレクターの 1 つも同様です。
@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
、この要素を Angular フォームで使用できます。
そうでなければ、独自の実装を用意する必要がありますControlValueAccessor
コントロール値アクセサ
Angularドキュメントには次のように記載されています
ControlValueAccessor は、Angular フォーム API と DOM 内のネイティブ要素間のブリッジとして機能します。
シンプルな Angular2 アプリケーションで次のテンプレートを記述してみましょう。
<input type="text" [(ngModel)]="userName">
上記の動作を理解するには、input
この要素にどのディレクティブが適用されるかを知る必要があります。ここで、Angular はエラーとともにヒントを提供します。
処理されない Promise 拒否: テンプレート解析エラー: 'input' の既知のプロパティではないため、'ngModel' にバインドできません。
さて、SOを開いて答えを得ることができます:FormsModule
にインポートします@NgModule
:
@NgModule({
imports: [
...,
FormsModule
]
})
export AppModule {}
インポートすると、すべて意図したとおりに動作します。しかし、内部では何が起こっているのでしょうか?
FormsModule は次のディレクティブをエクスポートします。
@NgModule({
...
exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
})
export class FormsModule {}
調査の結果、3つの指令が適用されることがわかりました。input
Ngコントロールステータス
@Directive({ セレクター: '[formControlName],[ngModel],[formControl]', ... }) エクスポートクラス NgControlStatus は AbstractControlStatus を拡張します { ... }
Ngモデル
@Directive({ セレクタ: '[ngModel]:not([formControlName]):not([formControl])', プロバイダ: [formControlBinding], exportAs: 'ngModel' }) export class NgModel extends NgControl implements OnChanges,
デフォルト値
@Directive({ セレクタ: `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-touched
、ng-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
メソッドを呼び出します。ControlValueAccessor
コールバックをonChange
(2)input
プロパティに保存し、イベントが発生したときにこのコールバックを起動します(3)。そして最後に、updateControl
コールバック内で関数が呼び出されます(4)
function updateControl(control: FormControl, dir: NgControl): void {
dir.viewToModelUpdate(control._pendingValue);
if (control._pendingDirty) control.markAsDirty();
control.setValue(control._pendingValue, {emitModelToViewChange: false});
}
ここで、Angular はフォーム API を呼び出しますcontrol.setValue
。
これが仕組みの簡単な説明です。