Angular中的ngDefaultControl是什么?


102

不,这不是重复的问题。您会看到,SO和Github中存在大量的问题,这些问题规定我将此指令添加到具有[(ngModel)]指令且未包含在表单中的标记中。如果不添加,则会出现错误:

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

好的,如果我将此属性放在那里,错误就会消失。可是等等!没人知道它在做什么!Angular的文档根本没有提到它。当我知道不需要值访问器时,为什么需要它?此属性如何与值访问者连接?该指令有什么作用?什么是价值继承人,我该如何使用它?

为什么每个人都继续做自己都不了解的事情?只需添加此行代码即可,谢谢,这不是编写好的程序的方法。

然后。我读了不是一个而是2个有关的角形式的巨大引导有关部分ngModel

你知道吗?没有一个提及价值访问者或ngDefaultControl。它在哪里?

Answers:


180

[ngDefaultControl]

第三方控件要求a ControlValueAccessor以角形式起作用。它们中的许多元素(例如Polymer的元素)的<paper-input>行为都类似于本<input>机元素,因此可以使用DefaultValueAccessor。添加ngDefaultControl属性将允许他们使用该指令。

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

要么

<paper-input ngDefaultControl formControlName="name">

因此,这就是引入这种属性的主要原因。

在angular2的alpha版本中称为ng-default-control属性。

因此ngDefaultControlDefaultValueAccessor指令的选择器之一是:

@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">

要了解我们input上面的行为,我们需要知道将哪些指令应用于此元素。在这里,角度会给出一些错误提示:

未处理的承诺拒绝:模板解析错误:由于它不是'input'的已知属性,因此无法绑定到'ngModel'。

好的,我们可以打开SO并获取答案:导入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)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我们可以在这里省略。


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

角度调用形成API的地方control.setValue

这是其工作方式的简短版本。


我只是做了@Input() ngModel@Output() ngModelChange双向结合,我想这应该是足够的桥梁。这看起来像以完全不同的方式执行相同的操作。也许我不应该为自己的领域命名ngModel
Gherman

2
如果您不将此组件与角度形式一起使用,则可以只创建自己的双向绑定@Input() value; @Output() valueChange: EventEmitter<any> = new EventEmitter();,然后仅使用[(value)]="someProp"
yurzui

1
那正是我在做什么。但是我将我的“值”命名为ngModel,Angular开始向我抛出错误并要求使用ControlValueAccessor。
Gherman

是否有人在vue和React中相当于ngDefaultControl?我的意思是,我使用控制值访问器在angular中创建了一个自定义输入组件,并将其包装为Angular元素中的Web组件。在同一项目中,我必须使用ngDefaultControl使其与角度形式一起使用。但是我应该怎么做才能使它们在Vue和React中工作呢?还在原生JS中吗?
卡文达(Kavinda Jayakody)

我在自定义组件上使用ngDefaultControl,但遇到一个问题。当我在formBuilder视图(自定义输入组件)中设置formControl的默认值时,仅模型不更新。我究竟做错了什么?
伊戈尔·扬科维奇(IgorJanković)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.