[ngDefaultControl]
Os controles de terceiros requerem um ControlValueAccessor
para 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 ngDefaultControl
atributo 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-control
atributo 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 DefaultValueAccessor
e 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 input
có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 FormsModule
para 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 {
NgControlStatus
directiva aulas apenas manipula como ng-valid
, ng-touched
, ng-dirty
e podemos omiti-lo aqui.
DefaultValueAccesstor
fornece NG_VALUE_ACCESSOR
token 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 {
NgModel
diretiva injeta um NG_VALUE_ACCESSOR
token 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 NgModel
vai injetar DefaultValueAccessor
. E agora a diretiva NgModel chama a setUpControl
funçã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:
NgModel
configura o controle (1) e chama o dir.valueAccessor !.registerOnChange
método. ControlValueAccessor
armazena o retorno de chamada na propriedade onChange
(2) e dispara esse retorno de chamada quando o input
evento acontece (3) . E, finalmente, a updateControl
funçã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() ngModel
e@Output() ngModelChange
para 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
?@Input() value; @Output() valueChange: EventEmitter<any> = new EventEmitter();
e, em seguida, usar[(value)]="someProp"
ngModel
e o Angular começou a lançar um erro para mim e a perguntar com ControlValueAccessor.