Nie, to nie jest powtórzone pytanie. Widzisz, jest mnóstwo pytań i problemów w SO i Githubie, które nakazują dodanie tej dyrektywy do tagu, który ma [(ngModel)]
dyrektywę i nie jest zawarty w formularzu. Jeśli tego nie dodam, pojawia się błąd:
ERROR Error: No value accessor for form control with unspecified name attribute
Ok, błąd znika, jeśli wstawię tam ten atrybut. Ale poczekaj! Nikt nie wie, co to robi! A doktor Angulara w ogóle o tym nie wspomina. Po co mi akcesor wartości, skoro wiem, że go nie potrzebuję? W jaki sposób ten atrybut jest powiązany z akcesoriami wartości? Co robi ta dyrektywa? Co to jest akcesor wartości i jak go używać?
I dlaczego wszyscy robią rzeczy, których w ogóle nie rozumieją? Po prostu dodaj tę linię kodu i działa, dziękuję, to nie jest sposób na pisanie dobrych programów.
I wtedy. Przeczytałem nie jeden, ale dwa obszerne przewodniki po formularzach w Angular oraz sekcję o ngModel
:
- https://angular.io/guide/forms
- https://angular.io/guide/reactive-forms
- https://angular.io/guide/template-syntax#ngModel
I wiesz co? Ani jednej wzmianki o akcesorach wartości lub ngDefaultControl
. Gdzie to jest?
źródło
Odpowiedzi:
[ngDefaultControl]
Kontrolki innych firm wymagają,
ControlValueAccessor
aby działały z formami kątowymi. Wiele z nich, podobnie jak Polymer<paper-input>
, zachowuje się jak<input>
element natywny i dlatego może używać rozszerzeniaDefaultValueAccessor
. DodaniengDefaultControl
atrybutu umożliwi im korzystanie z tej dyrektywy.<paper-input ngDefaultControl [(ngModel)]="value>
lub
<paper-input ngDefaultControl formControlName="name">
To jest główny powód, dla którego wprowadzono ten atrubut.
Nazywało się to
ng-default-control
atrybutem w wersjach alfa angular2 .Tak
ngDefaultControl
jest jednym z selektorów dyrektywy 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 {
Co to znaczy?
Oznacza to, że możemy zastosować ten atrybut do elementu (takiego jak komponent polimerowy), który nie ma własnego akcesorium wartości. Więc ten element będzie się zachowywał
DefaultValueAccessor
i możemy go używać z formami kątowymi.W przeciwnym razie musisz zapewnić własną implementację
ControlValueAccessor
ControlValueAccessor
Angular Docs stwierdza
Napiszmy następujący szablon w prostej aplikacji angular2:
<input type="text" [(ngModel)]="userName">
Aby zrozumieć, jak
input
zachowa się powyższe, musimy wiedzieć, które dyrektywy są zastosowane do tego elementu. Tutaj angular daje wskazówkę z błędem:Okay, możemy otworzyć SO i uzyskać odpowiedź: zaimportuj
FormsModule
do@NgModule
:@NgModule({ imports: [ ..., FormsModule ] }) export AppModule {}
Sprowadziliśmy to i wszystko działa zgodnie z przeznaczeniem. Ale co się dzieje pod maską?
FormsModule eksportuje dla nas następujące dyrektywy:
@NgModule({ ... exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES] }) export class FormsModule {}
Po pewnym zbadaniu możemy odkryć, że trzy dyrektywy zostaną zastosowane do naszego
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
Dyrektywa tylko manipuluje klas podobang-valid
,ng-touched
,ng-dirty
a możemy go pominąć tutaj.DefaultValueAccesstor
udostępniaNG_VALUE_ACCESSOR
token w tablicy dostawców: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
dyrektywa wprowadza doNG_VALUE_ACCESSOR
tokenu konstruktora, który został zadeklarowany w tym samym elemencie hosta.export NgModel extends NgControl implements OnChanges, OnDestroy { constructor(... @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
W naszym przypadku
NgModel
wstrzyknieDefaultValueAccessor
. A teraz dyrektywa NgModel wywołuje współdzielonąsetUpControl
funkcję: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); }); }
A oto most w akcji:
NgModel
ustawia control (1) i wywołujedir.valueAccessor !.registerOnChange
metodę.ControlValueAccessor
przechowuje wywołanie zwrotne we właściwościonChange
(2) i uruchamia to wywołanie zwrotne, gdyinput
nastąpi zdarzenie (3) . I wreszcieupdateControl
funkcja jest wywoływana wewnątrz callback (4)function updateControl(control: FormControl, dir: NgControl): void { dir.viewToModelUpdate(control._pendingValue); if (control._pendingDirty) control.markAsDirty(); control.setValue(control._pendingValue, {emitModelToViewChange: false}); }
gdzie wywołania kątowe tworzą API
control.setValue
.To krótka wersja tego, jak to działa.
źródło
@Input() ngModel
i@Output() ngModelChange
do wiązania dwukierunkowego i pomyślałem, że powinien wystarczyć mostek. To wygląda na zrobienie tego samego w zupełnie inny sposób. Może nie powinienem nazywać mojego polangModel
?@Input() value; @Output() valueChange: EventEmitter<any> = new EventEmitter();
a następnie po prostu użyć[(value)]="someProp"
ngModel
i Angular zaczął rzucać we mnie błąd i prosić o to z ControlValueAccessor.