不,这不是重复的问题。您会看到,SO 和 Github 中有大量问题和问题,规定我将此指令添加到具有[(ngModel)]指令且不包含在表单中的标签中。如果我不添加它,我会收到错误消息:
[(ngModel)]
ERROR Error: No value accessor for form control with unspecified name attribute
好的,如果我把这个属性放在那里,错误就会消失。可是等等!没有人知道它的作用!而 Angular 的文档根本没有提到它。当我知道我不需要值访问器时,为什么我需要它?该属性如何与值访问器相关联?这个指令有什么作用?什么是值访问器以及如何使用它?
为什么每个人都继续做他们根本不了解的事情?只需添加这行代码就可以了,谢谢,这不是编写好程序的方法。
接着。我阅读的不是一本而是 两 本关于 Angular 表单的大型指南, 以及 关于以下内容的部分ngModel:
ngModel
你知道吗?没有提到值访问器或ngDefaultControl. 它在哪里?
ngDefaultControl
第三方控件需要ControlValueAccessor具有角度形式的功能。它们中的许多,像 Polymer 的<paper- input>,表现得像<input>原生元素,因此可以使用DefaultValueAccessor. 添加ngDefaultControl属性将允许他们使用该指令。
ControlValueAccessor
<paper- input>
<input>
DefaultValueAccessor
<paper-input ngDefaultControl [(ngModel)]="value>
或者
<paper-input ngDefaultControl formControlName="name">
所以这就是引入这个属性的主要原因。
它在 angular2 的 alpha 版本中被称为ng- default- control属性。
ng- default- control
DefaultValueAccessor指令ngDefaultControl的选择器之一也是如此:
@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
Angular文档状态
ControlValueAccessor 充当 Angular 表单 API 和 DOM 中的原生元素之间的桥梁。
让我们在简单的 angular2 应用程序中编写以下模板:
<input type="text" [(ngModel)]="userName">
要了解input上面的行为方式,我们需要知道哪些指令应用于该元素。这里 angular 给出了一些错误提示:
input
未处理的承诺拒绝:模板解析错误:无法绑定到“ngModel”,因为它不是“输入”的已知属性。
好的,我们可以打开 SO 并得到答案:导入FormsModule到您的@NgModule:
FormsModule
@NgModule
@NgModule({ imports: [ ..., FormsModule ] }) export AppModule {}
我们按预期导入了它和所有作品。但是引擎盖下发生了什么?
FormsModule为我们导出以下指令:
@NgModule({ ... exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES] }) export class FormsModule {}
经过一番调查,我们可以发现三个指令将应用于我们的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可以在这里省略它。
NgControlStatus
ng-valid
ng-touched
ng-dirty
DefaultValueAccesstor``NG_VALUE_ACCESSOR在提供者数组中提供令牌:
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同一宿主元素上声明的构造函数令牌。
NgModel
NG_VALUE_ACCESSOR
export NgModel extends NgControl implements OnChanges, OnDestroy { constructor(... @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
在我们的例子NgModel中将注入DefaultValueAccessor. 现在 NgModel 指令调用共享setUpControl函数:
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)
dir.valueAccessor !.registerOnChange
onChange
updateControl
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。
control.setValue
这是它如何工作的简短版本。