不,这不是重复的问题。您会看到,SO 和 Github 中有大量问题和问题,规定我将此指令添加到具有 [(ngModel)]
指令且不包含在表单中的标记中。如果我不添加它,我会收到错误消息:
ERROR Error: No value accessor for form control with unspecified name attribute
好的,如果我把这个属性放在那里,错误就会消失。可是等等!没有人知道它的作用!而 Angular 的文档根本没有提到它。当我知道我不需要值访问器时,为什么我需要它?该属性如何与值访问器相关联?这个指令有什么作用?什么是值访问器以及如何使用它?
为什么每个人都继续做他们根本不了解的事情?只需添加这行代码就可以了,谢谢,这不是编写好程序的方法。
接着。我阅读的不是一个而是 两个 大量关于 Angular 表单的指南和一个关于 ngModel
的部分:
https://angular.io/guide/forms
https://angular.io/guide/reactive-forms
https://angular.io/guide/template-syntax#ngModel
你知道吗?没有提到值访问器或 ngDefaultControl
。它在哪里?
[ngDefaultControl]
第三方控件需要 ControlValueAccessor
才能使用角度形式。其中许多,如 Polymer 的 <paper-input>
,行为类似于 <input>
原生元素,因此可以使用 DefaultValueAccessor
。添加 ngDefaultControl
属性将允许他们使用该指令。
<paper-input ngDefaultControl [(ngModel)]="value>
或者
<paper-input ngDefaultControl formControlName="name">
所以这就是引入这个属性的主要原因。
它被称为 ng-default-control
属性 in alpha versions of angular2。
所以 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 表单 API 和 DOM 中的原生元素之间的桥梁。
让我们在简单的 angular2 应用程序中编写以下模板:
<input type="text" [(ngModel)]="userName">
要了解我们上面的 input
的行为方式,我们需要知道哪些指令应用于此元素。这里 angular 给出了一些错误提示:
未处理的承诺拒绝:模板解析错误:无法绑定到“ngModel”,因为它不是“输入”的已知属性。
好的,我们可以打开 SO 并得到答案:将 FormsModule
导入您的 @NgModule
:
@NgModule({
imports: [
...,
FormsModule
]
})
export AppModule {}
我们按预期导入了它和所有作品。但是引擎盖下发生了什么?
FormsModule 为我们导出以下指令:
@NgModule({
...
exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
})
export class FormsModule {}
https://i.stack.imgur.com/cHTxM.png
经过一番调查,我们可以发现三个指令将应用于我们的 input
1) NgControlStatus
@Directive({
selector: '[formControlName],[ngModel],[formControl]',
...
})
export class NgControlStatus extends AbstractControlStatus {
...
}
2)Ng模型
@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-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);
});
}
这是正在运行的桥梁:
https://i.stack.imgur.com/FLSNx.png
NgModel
设置控件(1) 并调用dir.valueAccessor !.registerOnChange
方法。 ControlValueAccessor
将回调存储在 onChange
(2) 属性中,并在 input
事件发生 (3) 时触发此回调。最后在回调 (4) 中调用 updateControl
函数
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
。
这是它如何工作的简短版本。
@Input() ngModel
和@Output() ngModelChange
,我认为它应该足够了。这看起来像是以完全不同的方式做同样的事情。也许我不应该将我的字段命名为ngModel
?@Input() value; @Output() valueChange: EventEmitter<any> = new EventEmitter();
,然后只需使用[(value)]="someProp"
ngModel
,Angular 开始向我抛出一个错误并要求使用 ControlValueAccessor。