Italian (Italiano) translation by Cinzia Sgariglia (you can also view the original English article)



I form sono fondamentali per qualsiasi applicazione moderna front-end, e sono un elemento che usiamo ogni giorno, anche se non lo sappiamo. I form sono necessari per far accedere in modo sicuro un utente all'app, per la ricerca di tutti gli hotel disponibili in una determinata città, per prenotare un taxi, per costruire una lista di cose da fare e fare tonnellate di altre cose a cui siamo abituati. Alcuni form hanno solo un paio di campi di input, mentre altri form potrebbero avere una matrice di campi che si estendono per un paio di pagine o schede.
In questo tutorial, parleremo di diverse strategie disponibili per lo sviluppo di form in Angular. Qualunque sia la strategia che scegliete, ecco le cose che una libreria di form dovrebbe coprire:
- Supportare il legame bidirezionale in modo che i valori del controllo di input sono sincronizzati con lo stato del componente.
- Tenere traccia dello stato del form e utilizzare segnali visivi per consentire all'utente di sapere se lo stato corrente è valido o no. Per esempio, se il nome utente contiene caratteri non validi, dovrebbe apparire un bordo rosso intorno al campo di input del nome utente.
- Avere un meccanismo per visualizzare correttamente gli errori di convalida.
- Abilitare o disabilitare determinate parti del form, a meno che non sono soddisfatti alcuni criteri di convalida.
Introduzione ai form in Angular
Angular, essendo un vero e proprio framework front-end, ha un proprio set di librerie per la creazione di form complessi. L'ultima versione di Angular ha due potenti strategie per la costruzione del form. Sono:
- i form template driven
- i form model driven o reactive
Entrambe le tecnologie appartengono alla libreria @angular/forms
e sono basate sulle stesse classi di controllo del form. Tuttavia, essi differiscono notevolmente nella loro filosofia, stile di programmazione e tecnica. Scegliere uno rispetto all'altro dipende dal vostro gusto personale e anche dalla complessità del form che state tentando di creare. A mio parere, dovreste provare entrambi gli approcci prima e poi scegliere uno che si adatta al vostro stile e al progetto sottomano.
La prima parte del tutorial coprirà i form template-driven con un esempio pratico: la costruzione di un modulo di iscrizione con convalida per tutti i campi del modulo. Nella seconda parte di questo tutorial, ripercorreremo i passaggi per creare lo stesso form utilizzando invece un approccio model-driven.
I form Template-Driven
L'approccio basato sul template-driven è una strategia che è stato preso in prestito dall'epoca di AngularJS. A mio parere, è il metodo più semplice per la creazione di form. Come funziona? Useremo alcune direttive di Angular.
Le direttive vi consentono di collegare il comportamento agli elementi nel DOM.
— Documentazione di Angular
Angular fornisce direttive specifiche per i form che è possibile utilizzare per associare i dati di input del modulo e il modello. Le direttive specifiche per i form aggiungono funzionalità extra e comportamento a un semplice form HTML. Il risultato finale è che il modello si occupa dei valori collegati con la convalida del modello e del form.
In questo tutorial, useremo il form template-driven per creare la pagina di iscrizione a un'applicazione. Il modulo coprirà gli elementi più comuni dei form e diversi controlli di convalida su questi elementi del form. Qui ci sono i passi che si seguiranno in questo tutorial.
- Aggiungere FormsModule a
app.module.ts
. - Creare una classe per il modello utente.
- Creare i componenti iniziali e il layout per il modulo di iscrizione.
- Usare le direttive di Angular per i form come
ngModel
,ngModelGroup
engForm
. - Aggiungere la convalida utilizzando i validatori integrati.
- Visualizzare significativamente gli errori di convalida.
- Gestire l'invio di moduli utilizzando
ngSubmit
.
Iniziamo.
Prerequisiti
Il codice per questo progetto è disponibile sul mio repo di GitHub. Scaricate lo zip o clonate il repo per vederlo in azione. Se si preferisce iniziare da zero invece, assicuratevi di aver installato Angular CLI. Utilizzate il comando ng
per generare un nuovo progetto.
$ ng new SignupFormProject
Successivamente, generate un nuovo componente per il SignupForm.
ng generate component SignupForm
Sostituite il contenuto di app.component.html con questo:
<app-signup-form> </app-signup-form>
Ecco la struttura della directory per la directory src /. Ho rimosso alcuni file non essenziali per mantenere le cose semplici.
. ├── app │ ├── app.component.css │ ├── app.component.html │ ├── app.component.ts │ ├── app.module.ts │ ├── signup-form │ │ ├── signup-form.component.css │ │ ├── signup-form.component.html │ │ └── signup-form.component.ts │ └── User.ts ├── index.html ├── main.ts ├── polyfills.ts ├── styles.css ├── tsconfig.app.json └── typings.d.ts
Come potete vedere, è stata creata automaticamente una directory per il componente SignupForm
. Che è dove andrà la maggior parte del nostro codice. Ho anche creato un nuovo User.ts
per memorizzare il nostro modello utente.
Il template HTML
Prima di tuffarci nel template del componente, abbiamo bisogno di avere un'idea astratta di quello che stiamo costruendo. Così ecco la struttura del form che ho nella mia mente. Il modulo di iscrizione avrà diversi campi di input, un elemento select e un elemento checkbox.
Ecco il modello HTML che useremo per la nostra pagina di iscrizione.
Template HTML
<div class="row custom-row"> <div class= "col-sm-5 custom-container jumbotron"> <form class="form-horizontal"> <fieldset> <legend>SignUp</legend> <!--- Email Block ---> <div class="form-group"> <label for="inputEmail">Email</label> <input type="text" id="inputEmail" placeholder="Email"> </div> <!--- Password Block ---> <div class="form-group"> <label for="inputPassword">Password</label> <input type="password" id="inputPassword" placeholder="Password"> </div> <div class="form-group"> <label for="confirmPassword" >Confirm Password</label> <input type="password" id="confirmPassword" placeholder="Password"> </div> <!--- Select gender Block ---> <div class="form-group"> <label for="select">Gender</label> <select id="select"> <option>Male</option> <option>Female</option> <option>Other</option> </select> </div> <!--- Terms and conditions Block ---> <div class="form-group checkbox"> <label> <input type="checkbox"> Confirm that you've read the Terms and Conditions </label> </div> <!--- Buttons Block ---> <div class="form-group"> <button type="reset" class="btn btn-default">Cancel</button> <button type="submit" class="btn btn-primary">Submit</button> </div> </fieldset> </form> </div> </div>
Le classi CSS utilizzate nel modello HTML sono parte della libreria Bootstrap utilizzata per fare cose carine. Poiché questa non è un un'esercitazione di disegno, non parlerò molto degli aspetti CSS del modulo a meno che non sia necessario.
Installazione del form di base
Per utilizzare le direttive del form template-driven, abbiamo bisogno di importare FormsModule
da @angular/forms
e aggiungerlo nella matrice import
in app.module.ts
.
app/app.module.ts
import { FormsModule } from '@angular/forms'; @NgModule({ . . imports: [ BrowserModule, FormsModule ], . . }) export class AppModule { }
Successivamente, creare una classe che conterrà tutte le proprietà dell'entità utente. Noi possiamo utilizzare un'interfaccia e implementarla nel componente oppure utilizzare una classe dattiloscritto per il modello.
app/User.ts
export class User { id: number; email: string; //Both the passwords are in a single object password: { pwd: string; confirmPwd: string; }; gender: string; terms: boolean; constructor(values: Object = {}) { //Constructor initialization Object.assign(this, values); } }
Ora, creare un'istanza della classe del componente SignupForm. Ho anche dichiarato una proprietà aggiuntiva per il genere.
app/signup-form/signup-form.component.ts
import { Component, OnInit } from '@angular/core'; // Import the User model import { User } from './../User'; @Component({ selector: 'app-signup-form', templateUrl: './signup-form.component.html', styleUrls: ['./signup-form.component.css'] }) export class SignupFormComponent implements OnInit { //Property for the gender private gender: string[]; //Property for the user private user:User; ngOnInit() { this.gender = ['Male', 'Female', 'Others']; //Create a new user object this.user = new User({ email:"", password: { pwd: "" , confirm_pwd: ""}, gender: this.gender[0], terms: false}); } }
Per il file di signup-form.component.html, ho intenzione di utilizzare lo stesso modello HTML discusso sopra, ma con modifiche minori. Il modulo di iscrizione ha un campo selezionato con un elenco di opzioni. Anche se funziona, lo faremo al modo di Angular scorrendo l'elenco tramite la direttiva ngFor
.
app/signup-form/signup-form.component.html
<div class="row custom-row"> <div class= "col-sm-5 custom-container jumbotron"> <form class="form-horizontal"> <fieldset> <legend>SignUp</legend> . . <!--- Gender Block --> <div class="form-group"> <label for="select">Gender</label> <select id="select"> <option *ngFor = "let g of gender" [value] = "g"> {{g}} </option> </select> </div> . . </fieldset> </form> </div> </div>
Poi, vogliamo associare i dati del form all'oggetto della classe utente, in modo che quando immettete i dati registrati nel modulo, viene creato un nuovo oggetto utente che archivia temporaneamente i dati. In questo modo, potete mantenere la vista sincronizzata con il modello, e questa operazione è detta binding.
Ci sono un paio di modi per far sì che questo accada. Permettetemi di introdurvi prima ngModel
e ngForm
.
ngForm e ngModel
ngForm e ngModel sono direttive di Angular che sono essenziali per creare form template-driven. Iniziamo prima con ngForm
. Ecco un estratto su ngForm dalla documentazione di Angular.
La direttivaNgForm
integra l'elementoform
con funzionalità aggiuntive. Essa detiene i controlli creati per gli elementi con una direttivangModel
e attributoname
e monitora le loro proprietà, inclusa la loro validità. Ha anche una propria proprietàvalid
che restituisce true solo se ogni controllo contenuto è valido.
In primo luogo, aggiornate il modulo con la direttiva ngForm
:
app/signup-form/signup-form.component.html
<form class="form-horizontal" #signupForm = "ngForm"> . . </form>
#signupForm
è una variabile di riferimento del template che fa riferimento alla direttiva ngForm
che governa l'intero modulo. Nell'esempio seguente viene illustrato l'utilizzo di un oggetto di riferimento ngForm
per la convalida.
app/signup-form/signup-form.component.html
<button type="submit" class="btn btn-success" [disabled]="!signupForm.form.valid"> Submit </button>
Qui, signupForm.form.valid
restituirà false a meno che tutti gli elementi del modulo passano i rispettivi controlli di convalida. Il pulsante di invio verrà disattivato fino a quando il modulo è valido.
Per quanto riguarda il binding del template e del modello, ci sono un sacco di modi per fare questo, e ngModel
ha tre diverse sintassi per affrontare questa situazione. Sono:
- [(ngModel)]
- [ngModel]
- ngModel
Cominciamo con il primo.
Il binding bidirezionale usando [(ngModel)]
[(ngModel)]
esegue il binding bidirezionale per la lettura e la scrittura di valori di controllo di input. Se viene utilizzata una direttiva [(ngModel)]
, il campo di input prende un valore iniziale dalla classe componente associata e lo aggiorna ogni volta che qualsiasi modifica del valore di controllo dell'input viene rilevato (azionando un tasto e premendo un pulsante). L'immagine qui sotto descrive meglio il processo di binding bidirezionale.



Ecco il codice per il campo di input e-mail:
<div class="form-group"> <label for="inputEmail">Email</label> <input type="text" [(ngModel)] = "user.email" id="inputEmail" name="email" placeholder="Email"> </div>
[(ngModel)] = "user.email"
collega la proprietà email dell'utente al valore di input. Ho anche aggiunto un attributo name e impostato name = "email"
. Questo è importante, e otterrete un errore se non avete dichiarato un attributo name durante l'utilizzo di ngModel.
Allo stesso modo, aggiungete un [(ngModel)]
e un attributo name univoco a ogni elemento del form. Il vostro modulo dovrebbe essere simile a questo ora:
app/signup-form/signup-form.component.html
. . . <div ngModelGroup="password"> <div class="form-group" > <label for="inputPassword">Password</label> <input type="password" [(ngModel)] = "user.password.pwd" name="pwd" placeholder="Password"> </div> <div class="form-group"> <label for="confirmPassword" >Confirm Password</label> <input type="password" [(ngModel)] = "user.password.confirmPwd" name="confirmPwd" placeholder="Confirm Password"> </div> </div> <div class="form-group"> <label for="select">Gender</label> <select id="select" [(ngModel)] = "user.gender" name = "gender"> <option *ngFor = "let g of gender" [value] = "g"> {{g}} </option> </select> </div> . . .
ngModelGroup
viene utilizzato per raggruppare i campi di modulo simili, in modo che possiamo eseguire le convalide solo su quei campi del modulo. Dal momento che entrambi i campi password sono legati, li metteremo sotto un singolo ngModelGroup. Se tutto funziona come previsto, la proprietà del componente associato user
dovrebbe essere responsabile della memorizzazione di tutti i valori di controllo del modulo. Per vederla in azione, è necessario aggiungere la seguente dopo il tag form:
{{user | json}}
Reindirizzate la proprietà utente attraverso JsonPipe
per eseguire il rendering del modello come JSON nel browser. Questo è utile per il debug e l'autenticazione. Si dovrebbe vedere un output JSON come questo.



I valori stanno fluendo dalla vista al modello. E riguardo all'altro modo? Provate a inizializzare l'oggetto utente con alcuni valori.
app/signup-form/signup-form.component.ts
this.user = new User({ //initialized with some data email:"thisisfromthemodel@example.com", password: { pwd: "" , confirm_pwd: ""}, gender: this.gender[0] });
E automaticamente appaiono nella vista:
{ "email": "thisisfromthemodel@example.com", "password": { "pwd": "", "confirm_pwd": "" }, "gender": "Male" }
La sintassi di binding bidirezionale [(ngModel)]
consente di compilare moduli senza sforzo. Tuttavia, ha alcuni inconvenienti; quindi, c'è un approccio alternativo che utilizza ngModel
o [ngModel]
.
Aggiungere ngModel al mix
Quando ngModel
viene utilizzato, in realtà siamo responsabili dell'aggiornamento della proprietà del componente con i valori del controllo di input e viceversa. I dati di input non scorrono automaticamente nella proprietà utente del componente.
Così sostituite tutte le istanze di [(ngModel)] = " "
con ngModel
. Manterremo l'attributo name
perché tutte e tre le versioni di ngModel hanno bisogno dell'attributo name
per funzionare.
app/signup-form/signup-form.component.html
<div class="form-group"> <label for="inputEmail">Email</label> <input type="text" ngModel id="inputEmail" name="email" placeholder="Email"> </div>
Con ngModel
, il valore dell'attributo name diventerà una chiave dell'oggetto di riferimento di ngForm, signupForm
, che abbiamo creato in precedenza. Così, ad esempio, signupForm.value.email
memorizzerà il valore di controllo per l'id della posta elettronica.
Sostituite {{user | json}}
con {{signupForm.value | json}}
perché è dove tutti gli state sono archiviati in questo momento.
Il binding unidirezionale usando [ngModel]
E se avete bisogno di impostare lo stato iniziale del componente associato della classe? Ecco cosa fa per voi [ngModel]
.



Qui i dati passano dal modello alla vista. Apportate le seguenti modifiche alla sintassi per utilizzare il binding unidirezionale:
app/signup-form/signup-form.component.html
<div class="form-group"> <label for="inputEmail">Email</label> <input type="text" [ngModel] = "user.email" id="inputEmail" name="email" placeholder="Email"> </div>
Così quale approccio scegliereste? Se state usando [(ngModel)]
e ngForm
insieme, alla fine avrete due stati da mantenere — user
e signupForm.value
— e potrebbe essere una potenziale fonte di confusione.
{ "email": "thisisfromthemodel@example.com", "password": { "pwd": "thisispassword", "confirm_pwd": "thisispassword" }, "gender": "Male" } //user.value { "email": "thisisfromthemodel@example.com", "password": { "pwd": "thisispassword", "confirm_pwd": "thisispassword" }, "gender": "Male" } //signupForm.value
Quindi, consiglierei di utilizzare invece il metodo di binding unidirezionale. Ma questo sta a voi deciderlo.
Convalida e visualizzazione dei messaggi di errore
Ecco i nostri requisiti per la convalida.
- Tutti i controlli del form sono obbligatori.
- Disattivare il pulsante di invio fino a quando tutti i campi di input sono pieni.
- Il campo email deve contenere rigorosamente un id di posta elettronica.
- Il campo password deve avere una lunghezza minima di 8.
- La password e la conferma devono corrispondere.



Il primo è facile. È necessario aggiungere un attributo di convalida required
per ogni elemento del form come questo:
app/signup-form/signup-form.component.html
<input type="text" [ngModel] = "user.email" name="email" #email = "ngModel" placeholder="Email" required>
A parte l'attributo required
, ho esportato anche una nuova variabile di riferimento del modello #email
. Questo è solo per farvi accedere alla casella input di controllo di Angular dall'interno del modello stesso. La useremo per visualizzare errori e avvisi. Ora è possibile utilizzare la proprietà del pulsante disabilitata per disattivare il pulsante:
app/signup-form/signup-form.component.html
<button type="submit" class="btn btn-success" [disabled]="!signupForm.form.valid"> Submit </button>
Per aggiungere un vincolo sull'e-mail, utilizzate l'attributo pattern che funziona con i campi di input. I pattern vengono utilizzati per specificare le espressioni regolari come quella riportata di seguito:
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$"
Per il campo della password, tutto quello che dovrete fare è aggiungere un attributo minlength = " "
:
app/signup-form/signup-form.component.html
<input type="password" ngModel id="inputPassword" name="pwd" #pwd = "ngModel" placeholder="Password" minlength="8" required>
Per visualizzare gli errori, ho intenzione di utilizzare la direttiva condizionale ngIf
su un elemento div. Iniziamo con il campo di controllo di input per l'e-mail:
app/signup-form/signup-form.component.html
<div class="form-group"> <label for="inputEmail">Email</label> <input type="text" [ngModel] = "user.email" name="email" #email = "ngModel" id="inputEmail" placeholder="Email" pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$" required> </div> <!-- This is the error section --> <div *ngIf="email.invalid && (email.dirty || email.touched)" class="alert alert-danger"> <div *ngIf = "email.errors?.required"> Email field can't be blank </div> <div *ngIf = "email.errors?.pattern && email.touched"> The email id doesn't seem right </div> </div>
C'è molto da fare qui. Cominciamo con la prima riga della sezione di errore.
<div *ngIf="email.invalid && (email.dirty || email.touched)" class="alert alert-danger">
Ricordate la variabile #email
che abbiamo esportato in precedenza? Trasporta una quantità di informazioni sullo stato di controllo dell'input del campo email. Questo include: email.valid
, email.invalid
, email.dirty
, email.pristine
, email.touched
, email.untouched
ed email.errors
. L'immagine qui sotto descrive tali proprietà in dettaglio.



Così l'elemento div con il *ngIf
verrà visualizzato solo se l'email non è valida. Tuttavia, l'utente sarà accolto con errori sui campi di input vuoti ancor prima che abbiano la possibilità di modificare il modulo.
Per evitare questo scenario, abbiamo aggiunto la seconda condizione. Verrà visualizzato l'errore solo dopo che il controllo è stato visitato o il valore del controllo è stato modificato.
Gli elementi div annidati vengono utilizzati per coprire tutti i casi di errori di convalida. Usiamo email.errors
per controllare tutti i possibili errori di convalida e quindi di visualizzarli all'utente sotto forma di messaggi personalizzati. Ora, seguiamo la stessa procedura per gli altri elementi del modulo. Ecco come ho codificato la convalida per le password.
app/signup-form/signup-form.component.html
<div ngModelGroup="password" #userPassword="ngModelGroup" required > <div class="form-group"> <label for="inputPassword">Password</label> <input type="password" ngModel name="pwd" id="inputPassword" placeholder="Password" minlength ="8" required> </div> <div class="form-group"> <label for="confirmPassword" >Confirm Password</label> <input type="password" ngModel name="confirmPwd" id="confirmPassword" placeholder="Confirm Password"> </div> <div *ngIf="(userPassword.invalid|| userPassword.value?.pwd != userPassword.value?.confirmPwd) && (userPassword.touched)" class="alert alert-danger"> <div *ngIf = "userPassword.invalid; else nomatch"> Password needs to be more than 8 characters </div> <ng-template #nomatch > Passwords don't match </ng-template> </div> </div>
Questo sta cominciando a sembrare un po' disordinato. Angular ha un insieme limitato di attributi di convalida: required
, pattern
, maxlength
e minlength
. Per coprire qualsiasi altro scenario come quello del confronto delle password, dovrete fare affidamento su istruzioni condizionali annidate ngIf
come ho fatto sopra. O in teoria, creare una funzione di convalida personalizzata, che viene descritta nella terza parte di questa serie.
Nel codice sopra, ho usato la sintassi ngIf else
introdotta con l'ultima versione di Angular. Ecco come funziona:
<div *ngIf="isValid;else notvalid"> Valid content... </div> <ng-template #notValid>Not valid content...</ng-template>
Inviare il modulo tramite ngSubmit
Abbiamo quasi finito il modulo. Ora abbiamo bisogno di essere in grado di inviare il modulo, e il controllo dei dati del modulo dovrebbe essere affidato a un metodo componente, diciamo onFormSubmit()
.
app/signup-form/signup-form.component.ts
<form novalidate (ngSubmit)="onFormSubmit(signupForm)" #signupForm="ngForm"> ...
Ora, per il componente:
app/signup-form/signup-form.component.ts
... public onFormSubmit({ value, valid}: { value: User, valid: boolean }) { this.user = value; console.log( this.user); console.log("valid: " + valid); } ...
Demo finale
Ecco la versione finale dell'applicazione. Ho aggiunto alcune classi bootstrap per rendere il form carino.
Riepilogo
Abbiamo finito qui. In questo tutorial, abbiamo coperto tutto quello che dovete sapere sulla creazione di un modulo in Angular utilizzando l'approccio template-driven. I form template-driven sono popolari per la loro semplicità e facilità d'uso.
Tuttavia, se avete bisogno di compilare un form con un sacco di elementi form, questo approccio diventerà disordinato. Così, nel prossimo tutorial, tratteremo il modo model-driven di creazione dello stesso modulo.
Condividete i vostri pensieri nei commenti qui sotto.