什么是 Angular 中的 ngDefaultControl?

2022-08-30 01:39:55

不,这不是一个重复的问题。你看,SO和Github中有很多问题和问题,规定我将此指令添加到具有指令且未包含在表单中的标记中。如果我不添加它,我会收到一个错误:[(ngModel)]

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

好吧,如果我将此属性放在那里,错误就会消失。但是,等等!没有人知道它的作用!而Angular的纪录片根本没有提到它。为什么当我知道我不需要值访问器时,我需要它?此属性如何连接到值访问器?此指令有什么作用?什么是值访问器,如何使用它?

为什么每个人都在做他们根本不理解的事情?只需添加这行代码就可以了,谢谢,这不是编写好程序的方法。

然后。我读了两个关于Angular形式的巨大指南以及关于以下内容的一节:ngModel

你知道吗?没有提到值访问器或 .它在哪里?ngDefaultControl


答案 1

[ngDefaultControl]

第三方控件需要 使用角度窗体运行。它们中的许多,如聚合物的,表现得像原生元素,因此可以使用.添加属性将允许他们使用该指令。ControlValueAccessor<paper-input><input>DefaultValueAccessorngDefaultControl

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

<paper-input ngDefaultControl formControlName="name">

所以这就是引入这种attrubute的主要原因。

它在 angular2 的 alpha 版本中被称为属性。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

ControlValueAccessor

角度文档状态

ControlValueAccessor 充当 Angular 表单 API 和 DOM 中的本机元素之间的桥梁。

让我们在简单的angular2应用程序中编写以下模板:

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

要了解我们的上述行为方式,我们需要知道哪些指令应用于此元素。这里 angular 给出了一些带有错误的提示:input

未处理的承诺拒绝:模板解析错误:无法绑定到“ngModel”,因为它不是“输入”的已知属性。

好的,我们可以打开SO并获得答案:导入到您的:FormsModule@NgModule

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

我们导入了它,一切都按预期工作。但是引擎盖下发生了什么?

FormsModule 为我们导出以下指令:

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

enter image description here

经过一番调查,我们可以发现三个指令将适用于我们的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-validng-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指令调用共享函数:NgModelDefaultValueAccessorsetUpControl

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);
  });
}

这是运行中的桥梁:

enter image description here

NgModel设置控制 (1) 和调用方法。 将回调存储在 (2) 属性中,并在事件发生时触发此回调 (3)。最后函数在回调内部调用 (4)dir.valueAccessor !.registerOnChangeControlValueAccessoronChangeinputupdateControl

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

这是它工作原理的简短版本。


答案 2