Qué es ngDefaultControl en Angular?


No, esta no es una pregunta duplicada. Ves, hay un montón de preguntas y problemas en SO y Github que prescriben que agrego esta directiva a una etiqueta que tiene directiva [(ngModel)] y no está contenida en una forma. Si no lo agrego obtengo un error:

ERROR Error: No value accessor for form control with unspecified name attribute

Ok, el error desaparece si pongo este atributo allí. PERO, ¡espera! Nadie sabe lo que hace! Y el doctor de Angular no lo menciona en absoluto. ¿Por qué necesito un accessor de valor cuando sé que no lo necesito? Cómo es este atributo ¿conectado a los accesores de valor? ¿Qué hace esta directiva? ¿Qué es un valor acessor y cómo lo uso?

¿Y por qué todo el mundo sigue haciendo cosas que no entienden? Simplemente agregue esta línea de código y funciona, gracias, esta no es la manera de escribir buenos programas.

Y luego. No he leído una sino dos enormes guías sobre formas en Angular y una sección sobre ngModel:

¿Y sabes qué? Ni una sola mención de los accesores de valor o ngDefaultControl. ¿Dónde está?

Author: Gherman, 2017-09-28

1 answers

[ngDefaultControl]

Los controles de terceros requieren un ControlValueAccessor para funcionar con formas angulares. Muchos de ellos, como el Polímero <paper-input>, se comportan como el elemento nativo <input> y por lo tanto pueden usar el DefaultValueAccessor. Agregar un atributo ngDefaultControl les permitirá usar esa directiva.

<paper-input ngDefaultControl [(ngModel)]="value>

O

<paper-input ngDefaultControl formControlName="name">

Así que esta es la razón principal por la que se introdujo este attrubute.

Fue llamado ng-default-control atributo en las versiones alfa de angular2.

Así que ngDefaultControl es uno de los selectores para directiva 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 {

¿Qué significa?

Esto significa que podemos aplicar este atributo a un elemento(como un componente polimérico) que no tiene su propio valor accesor. Así que este elemento tomará comportamiento de DefaultValueAccessor y podemos usar este elemento con formas angulares.

De lo contrario, debe proporcionar su propia implementación de ControlValueAccessor

ControlValueAccessor

Angular docs estados

Un ControlValueAccessor actúa como un puente entre la API de Angular forms y un elemento nativo en el DOM.

Vamos a escribir la siguiente plantilla en la aplicación angular2 simple:

<input type="text" [(ngModel)]="userName">

Para entender cómo nuestro input anterior se comportará necesitamos saber qué directivas se aplican a este elemento. Aquí angular da alguna pista con el error:

Unhandled Promise rejection: Template parse errors: Can't bound to 'ngModel' ya que no es una propiedad conocida de 'input'.

Bien, podemos abrir ASÍ y obtener la respuesta: importar FormsModule a su @NgModule:

@NgModule({
  imports: [
    ...,
    FormsModule
  ]
})
export AppModule {}

Lo importamos y todo funciona según lo previsto. ¿Pero qué está pasando bajo el capó?

FormsModule exporta para nosotros las siguientes directivas:

@NgModule({
 ...
  exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
})
export class FormsModule {}

introduzca la descripción de la imagen aquí

Después de algunas investigaciones podemos descubrir que se aplicarán tres directivas a nuestro 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 la directiva simplemente manipula clases como ng-valid, ng-touched, ng-dirty y podemos omitirlo aquí.


DefaultValueAccesstor proporciona NG_VALUE_ACCESSOR token en la matriz de proveedores:

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 directiva inyecta en el token constructor NG_VALUE_ACCESSOR que fue declarado en el mismo elemento host.

export NgModel extends NgControl implements OnChanges, OnDestroy {
 constructor(...
  @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {

En nuestro caso NgModel inyectará DefaultValueAccessor. Y ahora la directiva ngModel llama a la función compartida setUpControl:

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);
  });
}

Y aquí está el puente en acción:

introduzca la descripción de la imagen aquí

NgModel configura el control (1) y llama al método dir.valueAccessor !.registerOnChange. ControlValueAccessor almacena la devolución de llamada en onChange(2) propiedad y dispara esta devolución de llamada cuando sucede el evento input (3). Y finalmente updateControl la función se llama 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});
}

Donde angular llama a forms API control.setValue.

Eso es un corto versión de cómo funciona.

 63
Author: yurzui,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-30 10:35:53