L'errore significa che Angular non sa cosa fare quando si inserisce formControla div. Per risolvere questo problema, hai due opzioni.
- Inserisci l'
formControlNameelemento on, che è supportato da Angular out of the box. Questi sono: input, textareae select.
- Si implementa l'
ControlValueAccessorinterfaccia. In questo modo, stai dicendo ad Angular "come accedere al valore del tuo controllo" (da cui il nome). O in termini semplici: cosa fare, quando si inserisce formControlNameun elemento, a cui naturalmente non è associato un valore.
Ora, implementare l' ControlValueAccessorinterfaccia può essere inizialmente un po 'scoraggiante. Soprattutto perché non c'è molta buona documentazione di questo là fuori e devi aggiungere molta piastra di caldaia al tuo codice. Vorrei quindi provare a scomporlo in alcuni passaggi semplici da seguire.
Sposta il controllo del modulo nel suo componente
Per implementare il ControlValueAccessor, è necessario creare un nuovo componente (o direttiva). Spostare lì il codice relativo al controllo del modulo. In questo modo sarà anche facilmente riutilizzabile. Avere un controllo già all'interno di un componente potrebbe essere la ragione in primo luogo, perché è necessario implementare l' ControlValueAccessorinterfaccia, perché altrimenti non sarà possibile utilizzare il componente personalizzato insieme ai moduli angolari.
Aggiungi la caldaia al tuo codice
L'implementazione ControlValueAccessordell'interfaccia è piuttosto dettagliata, ecco il bollettino che ne deriva:
import {Component, OnInit, forwardRef} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';
@Component({
selector: 'app-custom-input',
templateUrl: './custom-input.component.html',
styleUrls: ['./custom-input.component.scss'],
// a) copy paste this providers property (adjust the component name in the forward ref)
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
// b) Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {
// c) copy paste this code
onChange: any = () => {}
onTouch: any = () => {}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
// d) copy paste this code
writeValue(input: string) {
// TODO
}
Quindi cosa stanno facendo le singole parti?
- a) Consente ad Angular di sapere durante l'esecuzione che è stata implementata l'
ControlValueAccessorinterfaccia
- b) Assicurati di implementare l'
ControlValueAccessorinterfaccia
- c) Questa è probabilmente la parte più confusa. Fondamentalmente quello che stai facendo è dare ad Angular i mezzi per sovrascrivere le proprietà / i metodi della tua classe
onChangee onTouchcon la sua propria implementazione durante il runtime, in modo da poter chiamare quelle funzioni. Quindi questo punto è importante da capire: non è necessario implementare onChange e onTouch (oltre all'implementazione vuota iniziale). L'unica cosa che stai facendo con (c) è lasciare che Angular associ le proprie funzioni alla tua classe. Perché? Quindi puoi quindi chiamare i metodi onChangee onTouchforniti da Angular al momento opportuno. Vedremo come funziona di seguito.
- d) Vedremo anche come funziona il
writeValuemetodo nella prossima sezione, quando lo implementeremo. L'ho messo qui, quindi tutte le proprietà richieste ControlValueAccessorsono implementate e il tuo codice viene ancora compilato.
Implementare writeValue
Ciò che writeValuefa è fare qualcosa all'interno del componente personalizzato, quando il controllo del modulo viene modificato all'esterno . Ad esempio, se hai nominato il tuo componente di controllo del modulo personalizzato app-custom-inpute lo utilizzeresti nel componente padre in questo modo:
<form [formGroup]="form">
<app-custom-input formControlName="myFormControl"></app-custom-input>
</form>
quindi writeValueviene attivato ogni volta che il componente padre modifica in qualche modo il valore di myFormControl. Questo potrebbe essere ad esempio durante l'inizializzazione del form ( this.form = this.formBuilder.group({myFormControl: ""});) o su un reset del modulo this.form.reset();.
Ciò che generalmente si desidera fare se il valore del controllo del modulo cambia all'esterno, è scriverlo in una variabile locale che rappresenta il valore del controllo del modulo. Ad esempio, se il tuo CustomInputComponentruota attorno a un controllo modulo basato su testo, potrebbe apparire così:
writeValue(input: string) {
this.input = input;
}
e nel codice HTML di CustomInputComponent:
<input type="text"
[ngModel]="input">
Puoi anche scriverlo direttamente sull'elemento di input come descritto nei documenti angolari.
Ora hai gestito ciò che accade all'interno del tuo componente quando qualcosa cambia all'esterno. Ora diamo un'occhiata all'altra direzione. Come informi il mondo esterno quando qualcosa cambia all'interno del tuo componente?
Chiamando onChange
Il prossimo passo è informare il componente genitore delle modifiche all'interno del tuo CustomInputComponent. È qui che entrano in gioco le funzioni onChangee onTouchdi (c) dall'alto. Chiamando quelle funzioni è possibile informare l'esterno delle modifiche all'interno del componente. Per propagare le modifiche del valore verso l'esterno, è necessario chiamare onChange con il nuovo valore come argomento . Ad esempio, se l'utente digita qualcosa nel inputcampo nel componente personalizzato, si chiama onChangecon il valore aggiornato:
<input type="text"
[ngModel]="input"
(ngModelChange)="onChange($event)">
Se controlli di nuovo l'implementazione (c) dall'alto, vedrai cosa sta succedendo: Angular bound è la propria implementazione alla onChangeproprietà class. Tale implementazione prevede un argomento, ovvero il valore di controllo aggiornato. Quello che stai facendo ora è chiamare quel metodo e quindi far conoscere ad Angular il cambiamento. Angolare ora procederà e modificherà il valore del modulo all'esterno. Questa è la parte fondamentale in tutto questo. Hai detto ad Angular quando dovrebbe aggiornare il controllo del modulo e con quale valore chiamandoonChange . Gli hai dato i mezzi per "accedere al valore di controllo".
A proposito: il nome onChangeè stato scelto da me. Puoi scegliere qualsiasi cosa qui, ad esempio propagateChangeo simili. Comunque lo chiami, sarà la stessa funzione che accetta un argomento, che è fornito da Angular e che è legato alla tua classe dal registerOnChangemetodo durante il runtime.
Chiamata onTouch
Poiché i controlli del modulo possono essere "toccati", è necessario fornire anche ad Angular i mezzi per capire quando viene toccato il controllo del modulo personalizzato. Puoi farlo, hai indovinato, chiamando la onTouchfunzione. Quindi, per il nostro esempio qui, se vuoi rimanere conforme a come Angular lo sta facendo per i controlli del modulo predefiniti, dovresti chiamare onTouchquando il campo di input è sfocato:
<input type="text"
[(ngModel)]="input"
(ngModelChange)="onChange($event)"
(blur)="onTouch()">
Ancora onTouchuna volta, è un nome scelto da me, ma la sua funzione effettiva è fornita da Angular e richiede zero argomenti. Il che ha senso, dato che stai solo facendo sapere ad Angular, che il controllo del modulo è stato toccato.
Mettere tutto insieme
Quindi come appare quando si riuniscono tutti insieme? Dovrebbe sembrare come questo:
// custom-input.component.ts
import {Component, OnInit, forwardRef} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';
@Component({
selector: 'app-custom-input',
templateUrl: './custom-input.component.html',
styleUrls: ['./custom-input.component.scss'],
// Step 1: copy paste this providers property
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
// Step 2: Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {
// Step 3: Copy paste this stuff here
onChange: any = () => {}
onTouch: any = () => {}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
// Step 4: Define what should happen in this component, if something changes outside
input: string;
writeValue(input: string) {
this.input = input;
}
// Step 5: Handle what should happen on the outside, if something changes on the inside
// in this simple case, we've handled all of that in the .html
// a) we've bound to the local variable with ngModel
// b) we emit to the ouside by calling onChange on ngModelChange
}
// custom-input.component.html
<input type="text"
[(ngModel)]="input"
(ngModelChange)="onChange($event)"
(blur)="onTouch()">
// parent.component.html
<app-custom-input [formControl]="inputTwo"></app-custom-input>
// OR
<form [formGroup]="form" >
<app-custom-input formControlName="myFormControl"></app-custom-input>
</form>
Altri esempi
Moduli nidificati
Notare che gli Accessor Control Value NON sono lo strumento giusto per i gruppi di moduli nidificati. Per i gruppi di moduli nidificati è possibile semplicemente utilizzare un @Input() subforminvece. Control Accessor Gli accessori sono pensati per concludere controls, no groups! Vedi questo esempio su come utilizzare un input per un modulo nidificato: https://stackblitz.com/edit/angular-nested-forms-input-2
fonti