Подтвердить что ты не робот

Что такое ngDefaultControl в Angular?

Нет, это не дублирующий вопрос. Понимаете, в SO и Github много вопросов и проблем, которые предписывают, что я добавляю эту директиву в тег, который имеет директиву [(ngModel)] и не содержится в форме. Если я не добавлю его, я получаю сообщение об ошибке:

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

Хорошо, ошибка исчезает, если я помещаю этот атрибут. Но ждать! Никто не знает, что он делает! И Angular doc не упоминает об этом вообще. Зачем мне нужен аксессуар, когда я знаю, что мне это не нужно? Как этот атрибут связан с атрибутами стоимости? Что делает эта директива? Что такое ценитель стоимости и как его использовать?

И почему все продолжают делать то, что они не понимают? Просто добавьте эту строку кода, и она работает, спасибо, это не способ писать хорошие программы.

И потом. Я прочитал не один, а два огромных руководства о формах в Angular и разделе о ngModel:

И знаешь что? Ни одного упоминания ни о добавочных атрибутах, ни о значении ngDefaultControl. Где это?

4b9b3361

Ответ 1

[ngDefaultControl]

Элементы сторонних разработчиков требуют, чтобы ControlValueAccessor функционировал с формами angular. Многие из них, такие как Polymer <paper-input>, ведут себя как родной элемент <input> и поэтому могут использовать DefaultValueAccessor. Добавление атрибута ngDefaultControl позволит им использовать эту директиву.

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

или

<paper-input ngDefaultControl formControlName="name">

Итак, это основная причина, по которой этот attrubute был введен.

Он был назван атрибутом ng-default-control в альфа-версиях 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, и мы можем использовать этот элемент с формами angular.

В противном случае вы должны предоставить свою собственную реализацию ControlValueAccessor

ControlValueAccessor

Angular состояния документов

A ControlValueAccessor действует как мост между API форм angularи собственный элемент в DOM.

Запишите следующий шаблон в простом приложении angular2:

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

Чтобы понять, как будет выглядеть наш input выше, нам нужно знать, какие директивы применяются к этому элементу. Здесь angular выдает некоторый намек на ошибку:

Необработанное отклонение обещаний: ошибки анализа шаблона: не удается привязать к 'ngModel', поскольку он не является известным свойством 'input'.

Хорошо, мы можем открыть SO и получить ответ: import 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-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.

Это короткая версия того, как она работает.