[ngDefaultControl]
Os controles de terceiros requerem um ControlValueAccessorpara funcionar com formas angulares. Muitos deles, como o do Polymer <paper-input>, se comportam como o <input>elemento nativo e, portanto, podem usar o DefaultValueAccessor. Adicionar um ngDefaultControlatributo permitirá que eles usem essa diretiva.
<paper-input ngDefaultControl [(ngModel)]="value>
ou
<paper-input ngDefaultControl formControlName="name">
Portanto, esta é a principal razão pela qual este atributo foi introduzido.
Era chamado de ng-default-controlatributo nas versões alfa do angular2 .
Então, ngDefaultControlé um dos seletores para a diretiva 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 {
O que isso significa?
Isso significa que podemos aplicar este atributo a um elemento (como um componente de polímero) que não tem seu próprio acessador de valor. Portanto, este elemento assumirá o comportamento de DefaultValueAccessore podemos usar este elemento com formas angulares.
Caso contrário, você deve fornecer sua própria implementação de ControlValueAccessor
ControlValueAccessor
Angular docs states
Um ControlValueAccessor atua como uma ponte entre a API de formulários Angular e um elemento nativo no DOM.
Vamos escrever o seguinte modelo em um aplicativo angular2 simples:
<input type="text" [(ngModel)]="userName">
Para entender como nosso inputcódigo acima se comportará, precisamos saber quais diretivas são aplicadas a este elemento. Aqui o angular dá alguma dica com o erro:
Rejeição de promessa não tratada: Erros de análise de modelo: Não é possível vincular a 'ngModel', pois não é uma propriedade conhecida de 'input'.
Ok, podemos abrir o SO e obter a resposta: importar FormsModulepara o seu @NgModule:
@NgModule({
imports: [
...,
FormsModule
]
})
export AppModule {}
Nós importamos e tudo funciona como pretendido. Mas o que está acontecendo sob o capô?
O FormsModule exporta para nós as seguintes diretivas:
@NgModule({
...
exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
})
export class FormsModule {}

Após alguma investigação, podemos descobrir que três diretivas serão aplicadas ao nosso 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 {
NgControlStatusdirectiva aulas apenas manipula como ng-valid, ng-touched, ng-dirtye podemos omiti-lo aqui.
DefaultValueAccesstorfornece NG_VALUE_ACCESSORtoken na matriz de provedores:
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 {
NgModeldiretiva injeta um NG_VALUE_ACCESSORtoken de construtor que foi declarado no mesmo elemento de host.
export NgModel extends NgControl implements OnChanges, OnDestroy {
constructor(...
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
No nosso caso NgModelvai injetar DefaultValueAccessor. E agora a diretiva NgModel chama a setUpControlfunção compartilhada :
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);
});
}
E aqui está a ponte em ação:

NgModelconfigura o controle (1) e chama o dir.valueAccessor !.registerOnChangemétodo. ControlValueAccessorarmazena o retorno de chamada na propriedade onChange(2) e dispara esse retorno de chamada quando o inputevento acontece (3) . E, finalmente, a updateControlfunção é chamada dentro de callback (4)
function updateControl(control: FormControl, dir: NgControl): void {
dir.viewToModelUpdate(control._pendingValue);
if (control._pendingDirty) control.markAsDirty();
control.setValue(control._pendingValue, {emitModelToViewChange: false});
}
onde angular chama o forms API control.setValue.
Essa é uma versão resumida de como funciona.
@Input() ngModele@Output() ngModelChangepara ligação bidirecional e achei que deveria ser uma ponte suficiente. Isso parece fazer a mesma coisa de uma maneira completamente diferente. Talvez eu não deva nomear meu campongModel?