Introduzione alle forme in Angular 4 forme reattive

Cosa starai creando

Questa è la seconda parte della serie su Introduction to Forms in Angular 4. Nella prima parte, abbiamo creato un modulo utilizzando l'approccio basato su template. Abbiamo usato direttive come ngModel, ngModelGroup e ngForm per potenziare gli elementi del modulo. In questo tutorial, adotteremo un approccio diverso alla costruzione delle forme, la via reattiva. 

Forme reattive

Le forme reattive adottano un approccio diverso rispetto a quello delle forme basate su template. Qui, creiamo e inizializziamo il forma oggetti di controllo nella nostra classe componente. Sono oggetti intermedi che mantengono lo stato della forma. Li legheremo quindi al forma elementi di controllo nel modello.

L'oggetto di controllo del modulo ascolta qualsiasi modifica nei valori di controllo di input e si riflettono immediatamente nello stato dell'oggetto. Poiché il componente ha accesso diretto alla struttura del modello dati, tutte le modifiche possono essere sincronizzate tra il modello dati, l'oggetto controllo modulo e i valori di controllo di input. 

In pratica, se stiamo costruendo un modulo per l'aggiornamento del profilo utente, il modello dati è l'oggetto utente recuperato dal server. Per convenzione, questo è spesso memorizzato all'interno della proprietà dell'utente del componente (this.user). L'oggetto di controllo del modulo o il modello di modulo saranno associati agli elementi di controllo del modulo effettivo del modello.

Entrambi questi modelli dovrebbero avere strutture simili, anche se non sono identici. Tuttavia, i valori di input non devono essere trasferiti direttamente nel modello di dati. L'immagine descrive come l'input dell'utente dal modello raggiunge il modello di modulo.

Iniziamo.

Prerequisiti

Non è necessario aver seguito la prima parte di questa serie, perché la seconda parte abbia senso. Tuttavia, se sei nuovo ai moduli in Angular, ti consiglio vivamente di passare attraverso la strategia basata sui modelli. Il codice per questo progetto è disponibile sul mio repository GitHub. Assicurati di essere nel ramo giusto e poi scarica lo zip o, in alternativa, clonare il repository per vedere il modulo in azione. 

Se preferisci ripartire da zero, assicurati di aver installato la CLI Angolare. Utilizzare il ng comando per generare un nuovo progetto. 

$ ng nuovo SignupFormProject

Quindi, generare un nuovo componente per il SignupForm o crearne uno manualmente. 

ng genera il componente SignupForm

Sostituisci il contenuto di app.component.html con questo:

 

Ecco la struttura della directory per src / directory. Ho rimosso alcuni file non essenziali per semplificare le cose.

. ├── app │ ├── app.component.css │ ├── app.component.html │ ├── app.component.ts │ ├── app.module.ts │ ├── modulo di iscrizione │ │ ├ ── 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 puoi vedere, una directory per il SignupForm componente è stato creato automaticamente. Ecco dove andrà la maggior parte del nostro codice. Ho anche creato un nuovo User.ts per memorizzare il nostro modello utente.

Il modello HTML

Prima di immergerci nel modello di componente reale, abbiamo bisogno di avere un'idea astratta di ciò che stiamo costruendo. Quindi ecco la struttura del modulo 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 utilizzeremo per la nostra pagina di registrazione. 

Modello HTML

 
Iscriviti

Le classi CSS utilizzate nel modello HTML fanno parte della libreria Bootstrap utilizzata per rendere le cose carine. Poiché non si tratta di un tutorial di progettazione, non parlerò molto degli aspetti CSS del modulo, a meno che non sia necessario. 

Impostazione modulo di base

Per creare un modulo Reattivo, è necessario importare il file ReactiveFormsModule a partire dal @ angolari / forme e aggiungilo alla matrice delle importazioni in app.module.ts.

app / app.module.ts

// Importa ReactiveFormsModule import ReactiveFormsModule da '@ angular / forms'; @NgModule (... // Aggiungi il modulo alle importazioni dell'Array delle importazioni: [BrowserModule, ReactiveFormsModule ...) Esporta classe AppModule  

Quindi, creare un modello utente per il modulo di registrazione. Possiamo usare una classe o un'interfaccia per creare il modello. Per questo tutorial, ho intenzione di esportare una classe con le seguenti proprietà.

app / User.ts

classe di esportazione Utente id: numero; email: string; // Entrambe le password sono in una password per un singolo oggetto: pwd: string; confirmPwd: string; ; genere: stringa; termini: booleano; costruttore (valori: Object = ) // Inizializzazione costruttore Object.assign (this, values);  

Ora, crea un'istanza del modello Utente in SignupForm componente. 

app / iscrizione-forma / iscrizione-form.component.ts

import Component, OnInit da '@ angular / core'; // Importa l'importazione del modello utente Utente da './... / Utente'; @Component (selector: 'app-signup-form', templateUrl: './signup-form.component.html', styleUrls: ['./signup-form.component.css']) classe di esportazione SignupFormComponent implementa OnInit // Elenco di genere per l'elemento di controllo select private genderList: string []; // Proprietà per l'utente privato dell'utente: Utente; ngOnInit () this.genderList = ['Male', 'Female', 'Others']; 

Per il iscrizione-form.component.html file, ho intenzione di utilizzare lo stesso modello HTML discusso sopra, ma con modifiche minori. Il modulo di iscrizione ha un campo di selezione con un elenco di opzioni. Anche se funziona, lo faremo in modo angolare scorrendo la lista usando il comando ngFor direttiva.

app / iscrizione-forma / iscrizione-form.component.html

Iscriviti...
...

Nota: potresti ricevere un errore che dice Nessun provider per ControlContainer. L'errore appare quando un componente ha un

tag senza una direttiva formGroup. L'errore scomparirà dopo aver aggiunto una direttiva FormGroup nel tutorial.

Abbiamo a disposizione un componente, un modello e un modello di modulo. E adesso? È tempo di sporcarci le mani e di conoscere le API necessarie per creare moduli reattivi. Ciò comprende FormControl e FormGroup

Tracciamento dello stato tramite FormControl

Durante la costruzione di moduli con la strategia delle forme reattive, non si incontrano le direttive ngModel e ngForm. Al contrario, utilizziamo l'API FormControl e FormGroup sottostante.

Un FormControl è una direttiva utilizzata per creare un'istanza FormControl che è possibile utilizzare per tenere traccia dello stato di un particolare elemento del modulo e del relativo stato di convalida. Ecco come funziona FormControl:

/ * Importa FormControl prima * / import FormControl da '@ angular / forms'; / * Esempio di creazione di una nuova istanza di FormControl * / export class SignupFormComponent email = new FormControl (); 

e-mail è ora un'istanza FormControl e puoi associarla a un elemento di controllo di input nel modello come segue:

Iscriviti

L'elemento del modulo template è ora associato all'istanza FormControl nel componente. Ciò significa che qualsiasi modifica al valore del controllo di input viene riflessa all'altra estremità. 

Un costruttore di FormControl accetta tre argomenti, un valore iniziale, una matrice di validatori di sincronizzazione e un array di validatori asincroni e, come si potrebbe immaginare, sono tutti facoltativi. Qui tratteremo i primi due argomenti. 

import Validators da '@ angular / forms'; ... / * FormControl con valore iniziale e un validatore * / email = nuovo FormControl ('[email protected] ', Validators.required); 

Angular ha un set limitato di validatori integrati. I metodi di validazione popolari includono Validators.required, Validators.minLength, Validators.maxlength, e Validators.pattern. Tuttavia, per utilizzarli, devi prima importare l'API Validator.

Per il nostro modulo di registrazione, abbiamo più campi di controllo di input (per e-mail e password), un campo di selezione e un campo checkbox. Piuttosto che creare individui FormControl oggetti, non avrebbe più senso raggruppare tutti questi FormControls sotto un'unica entità? Questo è utile perché ora possiamo tenere traccia del valore e della validità di tutti gli oggetti sub-FormControl in un unico punto. Questo è ciò che FormGroup è per. Quindi registreremo un FormGroup padre con più FormControls secondari. 

Raggruppa più moduli di controllo con FormGroup

Per aggiungere un FormGroup, importalo prima. Quindi, dichiarare signupForm come proprietà della classe e inizializzarlo come segue:

app / iscrizione-forma / iscrizione-form.component.ts

// Importa l'API per creare un modulo import FormControl, FormGroup, Validators da '@ angular / forms'; export class SignupFormComponent implementa OnInit genderList: String []; signupForm: FormGroup; ... ngOnInit () this.genderList = ['Male', 'Female', 'Others']; this.signupForm = new FormGroup (email: nuovo FormControl (", Validators.required), pwd: new FormControl (), confirmPwd: new FormControl (), gender: new FormControl (), termini: new FormControl ()) 

Associare il modello FormGroup al DOM come segue: 

app / iscrizione-forma / iscrizione-form.component.html

  
Iscriviti
...

[formGroup] = "signupForm" dice ad Angular che vuoi associare questo modulo con il FormGroup dichiarato nella classe del componente. Quando vede Angular formControlName = "email", controlla un'istanza di FormControl con il valore della chiave e-mail all'interno del FormGroup padre. 

Allo stesso modo, aggiorna gli altri elementi del modulo aggiungendo a formControlName = "valore" attributo come abbiamo appena fatto qui.

Per vedere se tutto funziona come previsto, aggiungi quanto segue dopo il tag del modulo:

app / iscrizione-forma / iscrizione-form.component.html

 

Valore del modulo signupForm.value | json

Stato modulo signupForm.status | JSON

Condisci il SignupForm proprietà attraverso il JsonPipe per rendere il modello come JSON nel browser. Questo è utile per il debug e la registrazione. Dovresti vedere un output JSON come questo.

Ci sono due cose da notare qui:

  1. Il JSON non corrisponde esattamente alla struttura del modello utente che abbiamo creato in precedenza. 
  2. Il signupForm.status visualizza che lo stato del modulo è NON VALIDO. Questo dimostra chiaramente che il Validators.required nel campo di controllo della posta elettronica funziona come previsto. 

La struttura del modello di modulo e il modello di dati devono corrispondere. 

// Form model "email": "", "pwd": "", "confirmPwd": "", "gender": "", "terms": false // Modello utente "email": "" , "password": "pwd": "", "confirmPwd": "",, "gender": "", "terms": false

Per ottenere la struttura gerarchica del modello di dati, dovremmo usare un FormGroup nidificato. Inoltre, è sempre una buona idea avere elementi di moduli correlati in un singolo FormGroup. 

FormGroup nidificato

Crea un nuovo Gruppo Form per la password.

app / iscrizione-forma / iscrizione-form.component.ts

 this.signupForm = new FormGroup (email: nuovo FormControl (", Validators.required), password: nuovo FormGroup (pwd: new FormControl (), confirmPwd: new FormControl ()), gender: new FormControl (), termini : new FormControl ())

Ora, per associare il nuovo modello di modulo al DOM, apportare le seguenti modifiche:

app / iscrizione-forma / iscrizione-form.component.html

 

formGroupName = "password" esegue l'associazione per il FormGroup nidificato. Ora, la struttura del modello di modulo corrisponde ai nostri requisiti.

Valore del modulo: "email": "", "password": "pwd": null, "confirmPwd": null, "gender": null, "terms": null Stato modulo "INVALID"

In seguito, dobbiamo convalidare i controlli del modulo.

Convalida il modulo

Abbiamo una semplice convalida in atto per il controllo di input e-mail. Tuttavia, questo non è sufficiente. Ecco l'intera lista dei nostri requisiti per la convalida.

  • Tutti gli elementi di controllo della forma sono necessario.
  • Disattiva il pulsante di invio fino a quando lo stato del modulo è VALIDO.
  • Il campo email deve contenere rigorosamente un ID e-mail.
  • Il campo password deve avere una lunghezza minima di 8.

Il primo è facile. Inserisci Validator.required a tutti i FormControls nel modello di modulo. 

app / iscrizione-forma / iscrizione-form.component.ts 

 this.signupForm = new FormGroup (email: nuovo FormControl (", Validators.required), password: nuovo FormGroup (pwd: new FormControl (", Validators.required), confirmPwd: new FormControl (", Validators.required) ), gender: new FormControl (", Validators.required), // requiredTrue in modo che il campo terms sia valido solo se ha verificato i termini: new FormControl (", Validators.requiredTrue)))

Quindi, disabilitare il pulsante mentre il modulo è NON VALIDO.

app / iscrizione-forma / iscrizione-form.component.html

 

Per aggiungere un vincolo all'e-mail, puoi utilizzare l'impostazione predefinita Validators.email o creare un'abitudine Validators.pattern () che specifica espressioni regolari come quella seguente:

email: new FormControl (", [Validators.required, Validators.pattern ('[a-z0-9 ._% + -] + @ [a-z0-9 .-] + \. [az] 2,3  $ ')])

Utilizzare il minLength validatore per i campi password.

 password: nuovo FormGroup (pwd: new FormControl (", [Validators.required, Validators.minLength (8)]), confirmPwd: new FormControl (", [Validators.required, Validators.minLength (8)])),

Questo è tutto per la convalida. Tuttavia, la logica del modello di modulo appare disordinata e ripetitiva. Puliamo prima questo. 

Rifattorizzare il codice usando FormBuilder

Angular fornisce uno zucchero di sintassi per la creazione di nuove istanze di FormGroup e FormControl denominate FormBuilder. L'API FormBuilder non fa nulla di speciale oltre a quello che abbiamo trattato qui.

Semplifica il nostro codice e rende il processo di creazione di un modulo facile agli occhi. Per creare un FormBuilder, devi importarlo in iscrizione-form.component.ts e iniettare il FormBuilder nel costruttore.

app / iscrizione-forma / iscrizione-form.component.ts 

import FormBuilder, FormGroup, Validators da '@ angular / forms'; ... export class SignupFormComponent implementa OnInit signupForm: FormGroup; // Dichiara il signupForm // Inietta il formbuilder nel costruttore constructor (private fb: FormBuilder)  ngOnInit () ...

Invece di creare un nuovo FormGroup (), stiamo usando this.fb.group per costruire un modulo. Tranne la sintassi, tutto il resto rimane lo stesso.

app / iscrizione-forma / iscrizione-form.component.ts 

 ngOnInit () ... this.signupForm = this.fb.group (email: [", [Validators.required, Validators.pattern ('[a-z0-9 ._% + -] + @ [a-z0- 9 .-] + \. [Az] 2,3 $ ')]], password: this.fb.group (pwd: [", [Validators.required, Validators.minLength (8)]], confirmPwd : [", [Validators.required, Validators.minLength (8)]]), gender: [", Validators.required], terms: [", Validators.requiredTrue])

Visualizzazione degli errori di convalida 

Per visualizzare gli errori, userò la direttiva condizionale ngIf su un elemento div. Iniziamo con il campo di controllo di input per email:

 

Ci sono un paio di problemi qui. 

  1. Dove è stato non valido e incontaminato vieni da? 
  2. signupForm.controls.email.invalid è troppo lungo e profondo.
  3. L'errore non dice esplicitamente perché non è valido.

Per rispondere alla prima domanda, ogni FormControl ha alcune proprietà come non valido, valido, incontaminato, sporco, toccato, e intatto. Possiamo usarli per determinare se visualizzare o meno un messaggio di errore o un avviso. L'immagine sotto descrive ciascuna di queste proprietà in dettaglio.

Quindi l'elemento div con il * ngIf sarà reso solo se l'email non è valida. Tuttavia, l'utente verrà salutato con errori sui campi di input che sono vuoti anche prima che abbiano la possibilità di modificare il modulo. 

Per evitare questo scenario, abbiamo aggiunto la seconda condizione. L'errore verrà visualizzato solo dopo il controllo è stato visitato.

Per sbarazzarsi della lunga catena di nomi dei metodi (signupForm.controls.email.invalid), Aggiungerò un paio di metodi getter di stenografia. Questo li mantiene più accessibili e brevi. 

app / iscrizione-forma / iscrizione-form.component.ts 

export class SignupFormComponent implements OnInit ... get email () return this.signupForm.get ('email');  get password () return this.signupForm.get ('password');  get gender () return this.signupForm.get ('gender');  get terms () return this.signupForm.get ('terms'); 

Per rendere l'errore più esplicito, ho aggiunto le seguenti condizioni ngIf nidificate:

app / iscrizione-forma / iscrizione-form.component.html

 
Il campo email non può essere vuoto
L'e-mail id non sembra giusta

Noi usiamo email.errors per verificare tutti gli errori di convalida possibili e quindi visualizzarli all'utente sotto forma di messaggi personalizzati. Ora, segui la stessa procedura per gli altri elementi del modulo. Ecco come ho codificato la convalida per le password e il controllo dei termini di input.

app / iscrizione-forma / iscrizione-form.component.html

  
La password deve contenere più di 8 caratteri
...
Si prega di accettare i termini e le condizioni prima.

Invia il modulo utilizzando ngSubmit

Abbiamo quasi finito con il modulo. Manca la funzionalità di invio, che stiamo per implementare ora.

Al momento dell'invio del modulo, i valori del modello di modulo devono essere trasferiti nella proprietà utente del componente.

app / iscrizione-forma / iscrizione-form.component.ts

public onFormSubmit () if (this.signupForm.valid) this.user = this.signupForm.value; console.log (this.user); / * Qualsiasi logica di chiamata API tramite i servizi va qui * /

Avvolgendolo

Se hai seguito questa serie di tutorial fin dall'inizio, abbiamo avuto un'esperienza diretta con due tecnologie di creazione di form popolari in Angular. Le tecniche guidate da modelli e guidate dal modello sono due modi per ottenere la stessa cosa. Personalmente, preferisco usare le forme reattive per i seguenti motivi:

  • Tutta la logica di convalida del modulo si troverà in un unico posto, all'interno della classe del componente. Questo è molto più produttivo dell'approccio modello, in cui le direttive ngModel sono sparse sul modello.
  • A differenza dei moduli basati su modelli, i moduli basati su modello sono più facili da testare. Non è necessario ricorrere a librerie di test end-to-end per testare il modulo.
  • La logica di convalida entrerà nella classe del componente e non nel modello.
  • Per un modulo con un numero elevato di elementi del modulo, questo approccio ha qualcosa chiamato FormBuilder per semplificare la creazione di oggetti FormControl.

Abbiamo perso una cosa, e questo è scrivere un validatore per la mancata corrispondenza della password. Nella parte finale della serie, tratteremo tutto ciò che è necessario sapere sulla creazione di funzioni di validazione personalizzate in Angular. Rimani sintonizzato fino ad allora.

Nel frattempo, ci sono un sacco di quadri e librerie per tenerti occupato, con un sacco di articoli sul mercato Envato da leggere, studiare e usare.