Che cos'è ngDefaultControl in Angular?


102

No, questa non è una domanda duplicata. Vedi, ci sono un sacco di domande e problemi in SO e Github che prescrivono che io aggiunga questa direttiva a un tag che ha [(ngModel)]direttiva e non è contenuto in un modulo. Se non lo aggiungo ricevo un errore:

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

Ok, l'errore scompare se inserisco questo attributo lì. Ma aspetta! Nessuno sa cosa fa! E il documento di Angular non lo menziona affatto. Perché ho bisogno di una funzione di accesso a valori quando so che non ne ho bisogno? In che modo questo attributo è collegato alle funzioni di accesso al valore? Cosa fa questa direttiva? Cos'è un Value Accessor e come si usa?

E perché tutti continuano a fare cose che non capiscono affatto? Basta aggiungere questa riga di codice e funziona, grazie, questo non è il modo per scrivere buoni programmi.

E poi. Ho letto non una ma due enormi guide sui moduli in Angular e una sezione su ngModel:

E tu sai cosa? Non una sola menzione di funzioni di accesso al valore o ngDefaultControl. Dov'è?

Risposte:


180

[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 {}

inserisci qui la descrizione dell'immagine

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:

inserisci qui la descrizione dell'immagine

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.


Ho appena realizzato @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 campo ngModel?
Gherman

2
Se non usi questo componente con forme angolari, puoi semplicemente creare il tuo legame a due vie come @Input() value; @Output() valueChange: EventEmitter<any> = new EventEmitter();e poi usare[(value)]="someProp"
yurzui

1
È esattamente quello che stavo facendo. Ma ho chiamato il mio "valore" come ngModele Angular ha iniziato a lanciarmi un errore e chiedere con ControlValueAccessor.
Gherman

Qualcuno che cosa è l'equivalente di ngDefaultControl in vue e React? Voglio dire, ho creato un componente di input personalizzato in angolare utilizzando la funzione di accesso del valore di controllo e lo avvolgo come un componente web in elementi angolari. Nello stesso progetto ho dovuto usare ngDefaultControl per farlo funzionare con forme angolari. Ma cosa devo fare per farli funzionare in Vue e React? Anche in JS nativo?
Kavinda Jayakody

Sto usando ngDefaultControl sul mio componente personalizzato, ma ho un problema. Quando imposto il valore predefinito per formControl all'interno della mia vista formBuilder (componente di input personalizzato) non viene aggiornato, solo model. Che cosa sto facendo di sbagliato?
Igor Janković
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.