Cos’è ngDefaultControl in Angular?

No, questa non è una domanda duplicata. Vedete, ci sono un sacco di domande e problemi in SO e Github che prescrivono che aggiungo 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 va via se metto questo attributo lì. Ma aspetta! Nessuno sa cosa fa! E il documento di Angular non lo menziona affatto. Perché ho bisogno di un accesso di valore quando so che non ne ho bisogno? In che modo questo attributo è collegato agli accessori di valore? Cosa fa questa direttiva? Cos’è un valore acessor e come lo uso?

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

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

  • https://angular.io/guide/forms
  • https://angular.io/guide/reactive-forms
  • https://angular.io/guide/template-syntax#ngModel

E tu sai cosa? Non una singola menzione di entrambi gli accessor value o ngDefaultControl . Dov’è?

[NgDefaultControl]

I controlli di terze parti richiedono che ControlValueAccessor funzioni con forms angolari. Molti di essi, come Polymer, si comportano come l’elemento nativo e quindi possono utilizzare DefaultValueAccessor . L’aggiunta di un attributo ngDefaultControl consentirà loro di utilizzare tale direttiva.

  

Quindi questa è la ragione principale per cui è stata introdotta questa attrubute.

È stato chiamato attributo ng-default-control 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 all'elemento (come il componente polimerico) che non ha il proprio valore di accesso. Quindi questo elemento prenderà comportamento da DefaultValueAccessor e possiamo usare questo elemento con forms angolari.

Altrimenti devi fornire la tua implementazione di ControlValueAccessor

ControlValueAccessor

Stati angolari dei documenti

Un ControlValueAccessor funge da ponte tra l'API di forms angolari e un elemento nativo nel DOM.

Scriviamo il seguente modello nella semplice applicazione angular2:

  

Per capire come si comportano i nostri input sopra, dobbiamo sapere quali direttive sono applicate a questo elemento. Qui angular dà qualche suggerimento con l'errore:

Rifiuto di Promessa non gestita: Errori di analisi modello: imansible collegarsi a "ngModel" poiché non è una proprietà nota di "input".

Ok, possiamo aprire SO e ottenere la risposta: importa FormsModule al tuo @NgModule :

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

Lo abbiamo importato e tutto funziona come previsto. Ma cosa succede sotto il cofano?

FormsModule esporta per noi le seguenti direttive:

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

inserisci la descrizione dell'immagine qui

Dopo alcune indagini possiamo scoprire che tre direttive saranno 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 { 

NgControlStatus direttiva NgControlStatus manipola solo classi come ng-valid , ng-touched , ng-dirty e possiamo ometterla qui.


DefaultValueAccesstor fornisce il token NG_VALUE_ACCESSOR nell'array 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 { 

NgModel direttiva NgModel inietta nel token costruttore NG_VALUE_ACCESSOR che è stato dichiarato sullo stesso elemento host.

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

Nel nostro caso NgModel inietterà DefaultValueAccessor . E ora la direttiva NgModel chiama la funzione setUpControl 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 la descrizione dell'immagine qui

NgModel imposta il controllo (1) e chiama il metodo dir.valueAccessor !.registerOnChange . ControlValueAccessor memorizza la richiamata nella proprietà onChange (2) e triggers questa richiamata quando si verifica un evento di input (3) . E infine la funzione updateControl è chiamata all'interno del 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 chiamate angolari forms API control.setValue .

Questa è una versione breve di come funziona.