[ngDefaultControl]
I controlli di terze parti richiedono a ControlValueAccessor
per funzionare con forme angolari. Molti di loro, come Polymer's <paper-input>
, si comportano come l' <input>
elemento nativo e quindi possono utilizzare l'estensione DefaultValueAccessor
. L'aggiunta di un ngDefaultControl
attributo consentirà loro di utilizzare quella direttiva.
<paper-input ngDefaultControl [(ngModel)]="value>
o
<paper-input ngDefaultControl formControlName="name">
Quindi questo è il motivo principale per cui è stato introdotto questo attrubute.
Era chiamato ng-default-control
attributo nelle versioni alfa di angular2 .
Quindi ngDefaultControl
è uno dei selettori per la direttiva 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 {
Cosa significa?
Significa che possiamo applicare questo attributo a un elemento (come il componente polimerico) che non ha il proprio valore di accesso. Quindi questo elemento prenderà il comportamento da DefaultValueAccessor
e possiamo usare questo elemento con forme angolari.
Altrimenti devi fornire la tua implementazione di ControlValueAccessor
ControlValueAccessor
Stati di documenti angolari
Un ControlValueAccessor funge da ponte tra l'API dei moduli angolari e un elemento nativo nel DOM.
Scriviamo il seguente modello nella semplice applicazione angular2:
<input type="text" [(ngModel)]="userName">
Per capire come input
si comporterà quanto sopra abbiamo bisogno di sapere quali direttive vengono applicate a questo elemento. Qui angular dà qualche suggerimento con l'errore:
Rifiuto della promessa non gestita: errori di analisi del modello: impossibile eseguire il binding a "ngModel" poiché non è una proprietà nota di "input".
Ok, possiamo aprire SO e ottenere la risposta: importa FormsModule
nel tuo @NgModule
:
@NgModule({
imports: [
...,
FormsModule
]
})
export AppModule {}
L'abbiamo importato e tutto funziona come previsto. Ma cosa sta succedendo sotto il cofano?
FormsModule esporta per noi le seguenti direttive:
@NgModule({
...
exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
})
export class FormsModule {}
Dopo qualche indagine possiamo scoprire che tre direttive verranno applicate al nostro 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
direttiva classi solo manipola piace ng-valid
, ng-touched
, ng-dirty
e possiamo omettere qui.
DefaultValueAccesstor
fornisce il NG_VALUE_ACCESSOR
token nell'array dei provider:
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
la direttiva inietta il NG_VALUE_ACCESSOR
token del costruttore dichiarato sullo stesso elemento host.
export NgModel extends NgControl implements OnChanges, OnDestroy {
constructor(...
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
Nel nostro caso NgModel
inietterà DefaultValueAccessor
. E ora la direttiva NgModel chiama la setUpControl
funzione condivisa :
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);
});
}
Ed ecco il ponte in azione:
NgModel
imposta control (1) e chiama il dir.valueAccessor !.registerOnChange
metodo. ControlValueAccessor
memorizza il callback nella proprietà onChange
(2) e attiva questo callback quando input
si verifica un evento (3) . E infine la updateControl
funzione viene chiamata all'interno di callback (4)
function updateControl(control: FormControl, dir: NgControl): void {
dir.viewToModelUpdate(control._pendingValue);
if (control._pendingDirty) control.markAsDirty();
control.setValue(control._pendingValue, {emitModelToViewChange: false});
}
dove angolare chiama i moduli API control.setValue
.
Questa è una versione breve di come funziona.
@Input() ngModel
e@Output() ngModelChange
per la rilegatura bidirezionale e ho pensato che dovesse bastare un ponte. Sembra che si faccia la stessa cosa in un modo completamente diverso. Forse non dovrei nominare il mio campongModel
?