Що таке ngDefaultControl у кутовому?


102

Ні, це не повторне запитання. Розумієте, в SO та Github є багато питань і питань, які передбачають, що я додаю цю директиву до тегу, який має [(ngModel)]директиву і не міститься у формі. Якщо я не додаю його, я отримую помилку:

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

Гаразд, помилка відпадає, якщо я помістив туди цей атрибут. АЛЕ, почекай! Ніхто не знає, що це робить! І доктор Angular зовсім не згадує про це. Навіщо мені потрібен ціннісний аксесуар, коли я знаю, що він мені не потрібен? Як цей атрибут пов'язаний з ціннісними аксесуарами? Що робить ця директива? Що таке аксесуар значення та як ним користуватися?

І чому всі продовжують робити те, що вони не всі розуміють? Просто додайте цей рядок коду, і він працює, дякую, це не спосіб писати хороші програми.

І потім. Я прочитав не один, а два величезні довідники про форми у кутовій та розділ про ngModel:

А ви знаєте що? Жодної згадки ні про засоби доступу, ні про ngDefaultControl. Де це?

Відповіді:


180

[ngDefaultControl]

Сторонні органи управління вимагають ControlValueAccessorфункціонування з кутовими формами. Багато з них, як і полімерні <paper-input>, поводяться як <input>рідний елемент і тому можуть використовувати його DefaultValueAccessor. Додавання ngDefaultControlатрибуту дозволить їм використовувати цю директиву.

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

або

<paper-input ngDefaultControl formControlName="name">

Тож це головна причина, чому цей втручання було введено.

Його називали 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і ми можемо використовувати цей елемент з кутовими формами.

В іншому випадку ви повинні забезпечити власну реалізацію ControlValueAccessor

ControlValueAccessor

Кутові документи

ControlValueAccessor виступає мостом між API кутових форм та нативним елементом у DOM.

Запишемо наступний шаблон у простій програмі angular2:

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

Щоб зрозуміти, як inputповодиться наше вище, нам потрібно знати, які директиви застосовуються до цього елемента. Тут кутовий дає деякий натяк на помилку:

Невідпрацьоване відхилення обіцянки: Помилки аналізу шаблону: Неможливо прив’язати до 'ngModel', оскільки це не відома властивість 'input'.

Гаразд, ми можемо відкрити 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-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});
}

де кутові виклики формують API control.setValue.

Це коротка версія того, як це працює.


Я щойно зробив @Input() ngModelі @Output() ngModelChangeдля двосторонньої прив'язки, і я подумав, що це має бути досить містком. Це схоже на те, що робити те ж саме зовсім іншим способом. Можливо, я не повинен називати своє поле ngModel?
Герман

2
Якщо ви не використовуєте цей компонент з кутовими формами, ви можете просто створити власну двосторонню палітурку, як, @Input() value; @Output() valueChange: EventEmitter<any> = new EventEmitter();а потім просто скористатись[(value)]="someProp"
yurzui

1
Саме цим я і займався. Але я назвав своє "значення" як ngModelі Angular почав кидати на мене помилку і просити у ControlValueAccessor.
Герман

Хто-небудь, хто еквівалентний ngDefaultControl у vue та React? Я маю на увазі, я зробив спеціальний вхідний компонент у кутовому, використовуючи аксесуар керуючих значень і обернувши його як веб-компонент у кутових елементах. У тому ж проекті мені довелося використовувати ngDefaultControl, щоб зробити цю роботу з кутовими формами. Але що мені робити, щоб вони працювали у Vue та React? Також у рідному JS?
Кавінда Джаякоді

Я використовую ngDefaultControl для мого користувацького компонента, але я борюся з однією проблемою. Коли я встановлюю значення за замовчуванням для formControl всередині мого перегляду formBuilder (спеціальний компонент введення) не оновлюється, лише модель. Що я роблю неправильно?
Ігор Янкович
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.