[ngDefaultControl]
I controlli di terze parti richiedono a ControlValueAccessorper 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 ngDefaultControlattributo 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-controlattributo 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 DefaultValueAccessore 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 inputsi 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 FormsModulenel 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 {
NgControlStatusdirettiva classi solo manipola piace ng-valid, ng-touched, ng-dirtye possiamo omettere qui.
DefaultValueAccesstorfornisce il NG_VALUE_ACCESSORtoken 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 {
NgModella direttiva inietta il NG_VALUE_ACCESSORtoken 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 NgModelinietterà DefaultValueAccessor. E ora la direttiva NgModel chiama la setUpControlfunzione 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:

NgModelimposta control (1) e chiama il dir.valueAccessor !.registerOnChangemetodo. ControlValueAccessormemorizza il callback nella proprietà onChange(2) e attiva questo callback quando inputsi verifica un evento (3) . E infine la updateControlfunzione 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() ngModele@Output() ngModelChangeper 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?