L'errore significa che Angular non sa cosa fare quando si inserisce formControl
a div
. Per risolvere questo problema, hai due opzioni.
- Inserisci l'
formControlName
elemento on, che è supportato da Angular out of the box. Questi sono: input
, textarea
e select
.
- Si implementa l'
ControlValueAccessor
interfaccia. 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 formControlName
un elemento, a cui naturalmente non è associato un valore.
Ora, implementare l' ControlValueAccessor
interfaccia 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' ControlValueAccessor
interfaccia, perché altrimenti non sarà possibile utilizzare il componente personalizzato insieme ai moduli angolari.
Aggiungi la caldaia al tuo codice
L'implementazione ControlValueAccessor
dell'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'
ControlValueAccessor
interfaccia
- b) Assicurati di implementare l'
ControlValueAccessor
interfaccia
- 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
onChange
e onTouch
con 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 onChange
e onTouch
forniti da Angular al momento opportuno. Vedremo come funziona di seguito.
- d) Vedremo anche come funziona il
writeValue
metodo nella prossima sezione, quando lo implementeremo. L'ho messo qui, quindi tutte le proprietà richieste ControlValueAccessor
sono implementate e il tuo codice viene ancora compilato.
Implementare writeValue
Ciò che writeValue
fa è 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-input
e lo utilizzeresti nel componente padre in questo modo:
<form [formGroup]="form">
<app-custom-input formControlName="myFormControl"></app-custom-input>
</form>
quindi writeValue
viene 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 CustomInputComponent
ruota 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 onChange
e onTouch
di (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 input
campo nel componente personalizzato, si chiama onChange
con 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 onChange
proprietà 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 propagateChange
o simili. Comunque lo chiami, sarà la stessa funzione che accetta un argomento, che è fornito da Angular e che è legato alla tua classe dal registerOnChange
metodo 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 onTouch
funzione. Quindi, per il nostro esempio qui, se vuoi rimanere conforme a come Angular lo sta facendo per i controlli del modulo predefiniti, dovresti chiamare onTouch
quando il campo di input è sfocato:
<input type="text"
[(ngModel)]="input"
(ngModelChange)="onChange($event)"
(blur)="onTouch()">
Ancora onTouch
una 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() subform
invece. 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