L'approccio alternativo alle Template Driven Form è quello basato sulle Reactive Form, note anche con il nome di Model Driven Form. A differenza dell'approccio descritto nelle sezioni precedenti, in cui il ruolo preponderante nella definizione di una form veniva dato al markup all'interno del template del componente, nelle Reactive Form il peso principale è dato dal codice all'interno del componente stesso. Vediamo di chiarire il concetto riscrivendo la form di inserimento di un nuovo articolo tramite questo approccio.
Partiamo nuovamente dal markup puro e semplice della nostra form:
<form>
<div>
<label for="txtTitolo">Titolo</label>
<input type="text" id="txtTitolo">
</div>
<div>
<label for="txtAutore">Autore</label>
<input type="text" id="txtAutore">
</div>
<div>
<label for="txtTesto">Testo</label>
<textarea id="txtTesto"></textarea>
</div>
<button type="button">Invia</button>
</form>
Per implementare questa form in stile Model Driven o Reactive dobbiamo creare un modello che rappresenti la struttura del DOM nel nostro componente. Possiamo fare questo sfruttando i componenti di sistema FormGroup e FormControl, come mostrato nel seguente esempio:
import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
@Component({
selector: 'app-articolo-form-reactive',
templateUrl: './articolo-form-reactive.component.html',
styleUrls: ['./articolo-form-reactive.component.css']
})
export class ArticoloFormReactiveComponent {
myForm: FormGroup;
constructor() {
this.myForm = new FormGroup({
txtTitolo: new FormControl(),
txtAutore: new FormControl(),
txtTesto: new FormControl()
});
}
}
Come possiamo vedere dall'esempio, abbiamo definito una proprietà myForm
a cui abbiamo assegnato un'istanza di FormGroup
. FormGroup rappresenta una form ed è definita in termini dei controlli in essa contenuti. Infatti abbiamo passato al suo costruttore un oggetto con i tre elementi che compongono la nostra form. Ciascuno di questi tre elementi è un'istanza di FormControl.
A questo punto non ci resta che mettere in relazione quanto definito tramite codice con il markup del template associato. Per prima cosa importiamo il supporto per la gestione delle Reactive Form a livello di applicazione Angular e dichiariamo il nuovo componente:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ArticoloComponent } from './articolo';
import { AppComponent } from './app.component';
import { ArticoloFormReactiveComponent } from './articolo-form-reactive/articolo-form-reactive.component';
import {ReactiveFormsModule} from "@angular/forms";
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule
],
declarations: [
AppComponent,
ArticoloComponent,
ArticoloFormReactiveComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Abbiamo importato il componente ReactiveFormsModule dal modulo di sistema @angular/forms
e il componente ArticoloFormReactiveComponent
che abbiamo appena definito. ReactiveFormsModule fornisce alcune direttive che permettono di mettere in relazione il markup della nostra form con la sua definizione all'interno del componente, come mostrato di seguito:
<form [formGroup]="myForm">
<div>
<label for="txtTitolo">Titolo</label>
<input type="text" formControlName="txtTitolo">
</div>
<div>
<label for="txtAutore">Autore</label>
<input type="text" formControlName="txtAutore">
</div>
<div>
<label for="txtTesto">Testo</label>
<textarea formControlName="txtTesto"></textarea>
</div>
<button type="button">Invia</button>
</form>
Abbiamo utilizzato la direttiva formGroup per mappare la form sulla proprietà myForm
ed abbiamo utilizzato formControlName per mettere in corrispondenza ciascun elemento di input con il relativo controllo creato all'interno del componente.
Accedere al contenuto della form
Come possiamo notare, non abbiamo fatto uso di ngModel
, ma possiamo accedere ai valori inseriti dall'utente tramite la proprietà myForm
e i controlli associati. Ad esempio, possiamo gestire l'evento click sul pulsante associando il gestore visualizzaArticolo()
, come nel seguente esempio:
<button type="button" (click)="visualizzaArticolo()">Invia</button>
Possiamo visualizzare sulla console i dati di un articolo inseriti dall'utente nel seguente modo:
visualizzaArticolo() {
console.log(this.myForm.value);
}
La proprietà myForm.value
conterrà un oggetto composto dai valori attualmente presenti sulla form, come nel seguente esempio:
{
txtTitolo: "Reactive Forms",
txtAutore: "Mario Rossi",
txtTesto: "In alternativa alle template driven form..."
}
In alternativa possiamo accedere ai singoli controlli della form sfruttando la proprietà controls
di myForm
:
visualizzaArticolo() {
console.log(this.myForm.controls["txtTitolo"].value);
console.log(this.myForm.controls["txtAutore"].value);
console.log(this.myForm.controls["txtTesto"].value);
}
Definizione di form con FormBuilder
Per definire il modello della form all'interno del nostro componente abbiamo sfruttato la classe FormGroup
:
export class ArticoloFormReactiveComponent {
constructor() {
this.myForm = new FormGroup({
txtTitolo: new FormControl(),
txtAutore: new FormControl(),
txtTesto: new FormControl()
});
}
}
Il codice può risultare prolisso, soprattutto quando la form è complessa e in presenza di numerosi controlli. Un approccio più compatto per definire una form è basato sull'uso di FormBuilder. Il seguente codice produce lo stesso effetto del codice precedente, ma è molto più sintetico:
import {FormBuilder, FormGroup} from '@angular/forms';
...
export class ArticoloFormReactiveComponent {
constructor(fb: FormBuilder) {
this.myForm = fb.group({
txtTitolo: [],
txtAutore: [],
txtTesto: []
});
}
}
In pratica, abbiamo importato FormBuilder
nel modulo corrente e lo abbiamo iniettato nel costruttore (avremo modo di approfondire in seguito il concetto di dependency injection in Angular 2). Il metodo group()
di FormBuilder
ci consente di creare un'istanza di FormGroup
all'interno del quale abbiamo definito i soliti tre controlli.
Validazione dei controlli
Utilizzando FormBuilder
non abbiamo definito i tre controlli della form come FormControl
, ma come array vuoti. In realtà è proprio FormBuilder
che si occuperà di generare i relativi FormControl
, prendendo l'eventuale contenuto degli array come opzioni di creazione. Infatti, nella creazione di un controllo possiamo specificare alcune opzioni di inizializzazione come ad esempio il valore predefinito e l'eventuale validatore. Ad esempio, il seguente codice assegna un valore predefinito a ciascun controllo della form:
export class ArticoloFormReactiveComponent {
constructor(fb: FormBuilder) {
this.myForm = fb.group({
txtTitolo: ["Titolo dell'articolo"],
txtAutore: ["Autore dell'articolo"],
txtTesto: ["Testo dell'articolo"]
});
}
}
Mentre per assicurarci che venga obbligatoriamente inserito un valore per ciascun controllo della form dovremo far ricorso ai validatori:
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
...
export class ArticoloFormReactiveComponent {
constructor(fb: FormBuilder) {
this.myForm = fb.group({
txtTitolo: ["", Validators.required],
txtAutore: ["", Validators.required],
txtTesto: ["", Validators.required]
});
}
}
Importando il componente di sistema Validators abbiamo accesso ai validatori predefiniti di Angular. Nel nostro caso, il validatore Validators.required
rende valido il controllo quando l'utente ha inserito un valore. Possiamo specificare più validatori combinati tra loro tramite un array, come mostrato di seguito:
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
...
export class ArticoloFormReactiveComponent {
constructor(fb: FormBuilder) {
this.myForm = fb.group({
txtTitolo: ["", [ Validators.required,
Validators.maxLength(30)]
],
txtAutore: ["", [ Validators.required,
Validators.maxLength(20)]
],
txtTesto: ["", [ Validators.required,
Validators.minLength(2000),
Validators.maxLength(5000)]
]
});
}
}
In questo caso abbiamo stabilito dei vincoli sulla lunghezza del contenuto di ciascun controllo.
Angular 2 prevede validatori per i corrispondenti attributi di validazione di HTML 5. In particolare segnaliamo:
Validatore | Descrizione |
---|---|
required | impone che il controllo non abbia un valore nullo |
minLength | impone una lungezza minima di caratteri |
maxLength | limita ad una lunghezza massima di caratteri |
pattern | richiede che il valore del controllo rispetti l'espressione regolare specificata |
Possiamo dare un feedback all'utente sulla validità del controllo direttamente nel markup come nel seguente esempio:
<form [formGroup]="myForm">
<div>
<label for="txtTitolo">Titolo</label>
<input type="text" formControlName="txtTitolo">
<span [hidden]="myForm.controls.txtTitolo.valid || myForm.controls.txtTitolo.pristine">Il titolo è obbligatorio e non può superare i 30 caratteri!</span>
</div>
...
</form>
Come possiamo vedere, tramite myForm
abbiamo accesso ai controlli del modello della form ed alle proprietà valid e pristine, che ci danno informazioni sulla validità e sullo stato di modifica in modo analogo a quanto abbiamo visto a proposito delle form Template Driven.