Introduktion till formulär i vinkel 4 Reaktiva blanketter

Vad du ska skapa

Det här är den andra delen av serien om introduktion till former i vinkel 4. I den första delen skapade vi en blankett med hjälp av den mallstyrda metoden. Vi använde direktiv som ngModel, ngModelGroup och ngForm att överbelasta formelementen. I denna handledning tar vi ett annat sätt att bygga former - det reaktiva sättet. 

Reaktiva formulär

Reaktiva blanketter tar en annan inställning jämfört med de mallstyrda formulären. Här skapar och initierar vi bilda kontrollobjekt i vår komponentklass. De är mellanliggande föremål som håller formens tillstånd. Vi binder dem sedan till bilda kontrollelement i mallen.

Formkontrollobjektet lyssnar på alla ändringar i ingångskontrollvärdena, och de reflekteras omedelbart i objektets tillstånd. Eftersom komponenten har direkt tillgång till datamodellstrukturen kan alla ändringar synkroniseras mellan datamodellen, formkontrollobjektet och ingångskontrollvärdena. 

Praktiskt sett, om vi bygger ett formulär för uppdatering av användarprofilen, är datamodellen det användarobjekt som hämtas från servern. Enligt konventionen lagras det ofta i komponentens användaregenskap (this.user). Formkontrollobjektet eller formulärmodellen kommer att vara bunden till mallens faktiska styrelement.

Båda dessa modeller ska ha liknande strukturer, även om de inte är identiska. Inmatningsvärdena bör dock inte direkt flöda i datamodellen. Bilden beskriver hur användarens inmatning från mallen går till formulärmodellen.

Låt oss börja.

förutsättningar

Du behöver inte ha följt del 1 av den här serien, för del två är meningsfull. Men om du är ny på formulär i Angular, skulle jag verkligen rekommendera att gå igenom den mallstyrda strategin. Koden för det här projektet är tillgängligt på mitt GitHub-arkiv. Se till att du är på den högra grenen och sedan ladda ner zip eller, alternativt, klona repo för att se formuläret i aktion. 

Om du föredrar att börja från början istället, se till att du har installerat Angular CLI. Använd ng kommando för att skapa ett nytt projekt. 

$ ng nytt SignupFormProject

Skapa sedan en ny komponent för SignupForm eller skapa en manuellt. 

ng generera komponent SignupForm

Ersätt innehållet i app.component.html med detta:

 

Här är katalogstrukturen för src / katalogen. Jag har tagit bort några icke-nödvändiga filer för att hålla sakerna enkla.

. ├── app │ ├── app.component.css │ ├── app.component.html │ ├── app.component.ts │ ├── app.module.ts │ ├── registreringsformulär │ │ ├ ─ - 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 

Som du kan se, en katalog för SignupForm komponenten har skapats automatiskt. Det är där de flesta av vår kod kommer att gå. Jag har också skapat en ny User.ts för att lagra vår användarmodell.

HTML-mallen

Innan vi dyker in i själva komponentmallen behöver vi ha en abstrakt idé om vad vi bygger. Så här är den formstruktur som jag har i mitt sinne. Anmälningsformuläret kommer att ha flera inmatningsfält, ett väljelement och en kryssruta. 


Här är HTML-mallen som vi ska använda för vår registreringssida. 

HTML-mall

 
Bli Medlem

CSS-klasserna som används i HTML-mallen är en del av Bootstrap-biblioteket som används för att göra saker ganska. Eftersom detta inte är en designtutorial, kommer jag inte att prata mycket om CSS-aspekterna av formuläret om det inte behövs. 

Grundläggande formulärinställning

För att skapa en Reactive-formulär måste du importera ReactiveFormsModule från @ kantiga / former och lägg till den i importmatrisen i app.module.ts.

app / app.module.ts

// Importera ReactiveFormsModule import ReactiveFormsModule från '@ vinkel / former'; @NgModule (... // Lägg till modulen till importen Arrayimport: [BrowserModule, ReactiveFormsModule ...) exportklass AppModule  

Skapa sedan en användarmodell för registreringsformuläret. Vi kan antingen använda en klass eller ett gränssnitt för att skapa modellen. För denna handledning kommer jag att exportera en klass med följande egenskaper.

app / User.ts

exportklass Användare id: nummer; email: string; // Båda lösenorden finns i ett enda objektlösenord: pwd: string; confirmPwd: string; ; kön: sträng; termer: booleska; konstruktör (värden: Objekt = ) // Konstruktorinitialisering Object.assign (detta, värden);  

Skapa nu en förekomst av användarmodellen i SignupForm komponent. 

app / registrerings-formen / signup-form.component.ts

importera Component, OnInit från '@ vinkel / kärna'; // Importera användarnamporten Användare från './.../User'; @Component (selector: 'app-signup-form', templateUrl: './signup-form.component.html', styleUrls: ['./signup-form.component.css']) exportklass SignupFormComponent implementerar OnInit // Kön lista för välj kontrollelementet privat könLista: sträng []; // Egenskap för användarens privata användare: Användare; ngOnInit () this.genderList = ['Male', 'Female', 'Others']; 

För registrering-form.component.html fil, kommer jag att använda samma HTML-mall som diskuterats ovan, men med mindre ändringar. Anmälningsformuläret har ett väljfält med en lista med alternativ. Även om det fungerar, kommer vi att göra det på vinkeln genom att slingra igenom listan med hjälp av ngFor direktiv.

app / registrerings-formen / registrering-form.component.html

Bli Medlem...
...

Obs! Du kan få ett fel som säger Ingen leverantör för ControlContainer. Felet visas när en komponent har en

tagg utan ett formulärgrupps-direktiv. Felet försvinner när vi lägger till ett FormGroup-direktiv senare i handledningen.

Vi har en komponent, en modell och en formmall till hands. Vad nu? Det är dags att få våra händer smutsiga och bekanta med de API-er som du behöver för att skapa reaktiva former. Detta inkluderar FormControl och FormGroup

Spåra staten med FormControl

Samtidigt som du bygger formulär med den reaktiva formulärstrategin kommer du inte att överträffa ngModel och ngForm-direktiven. I stället använder vi det underliggande FormControl and FormGroup API.

En FormControl är ett direktiv som används för att skapa en FormControl-instans som du kan använda för att hålla reda på ett visst formelements tillstånd och dess valideringsstatus. Så här fungerar FormControl:

/ * Importera FormControl först * / import FormControl från '@ vinkel / formulär'; / * Exempel på att skapa en ny FormControl-förekomst * / exportklass SignupFormComponent email = new FormControl (); 

e-post är nu en FormControl-förekomst, och du kan binda den till ett ingångskontrollelement i din mall enligt följande:

Bli Medlem

Mallformelementet är nu kopplat till FormControl-förekomsten i komponenten. Vad det betyder är någon förändring till ingångskontrollvärdet återspeglas i andra änden. 

En FormControl-konstruktör accepterar tre argument - ett initialvärde, en rad synkroniseringsvaliderare och en rad async-validerare - och som du kanske har gissat är de alla valfria. Vi kommer att täcka de två första argumenten här. 

importera Validators från '@ vinkel / formulär'; ... / * FormControl med inledande värde och en validator * / email = new FormControl ('[email protected] ', Validators.required); 

Angular har en begränsad uppsättning inbyggda validatorer. De populära valideringsmetoderna inkluderar Validators.required, Validators.minLength, Validators.maxlength, och Validators.pattern. För att kunna använda dem måste du först importera Validator API.

För vår anmälningsblankett har vi flera inmatningskontrollfält (för e-post och lösenord), ett väljarkonto och ett kryssfält. Snarare än att skapa individ FormControl objekt, skulle det inte vara mer meningsfullt att gruppera alla dessa FormControls under en enda enhet? Detta är fördelaktigt eftersom vi nu kan spåra värdet och giltigheten för alla delformskontrollobjekt på ett ställe. Det är vad FormGroup är för. Så vi registrerar en förälder FormGroup med flera barn FormControls. 

Gruppera flera FormControls med FormGroup

För att lägga till en FormGroup, importera den först. Därefter förklara signupForm som en klassegenskap och initiera den enligt följande:

app / registrerings-formen / signup-form.component.ts

// Importera API: n för att bygga en formulärimport FormControl, FormGroup, Validators från '@ angle / forms'; exportklass SignupFormComponent implementerar OnInit genderList: String []; signupForm: FormGroup; ... ngOnInit () this.genderList = ['Man', 'Female', 'Others']; this.signupForm = new FormGroup (email: new FormControl ("Validators.required), pwd: new FormControl (), confirmPwd: new FormControl (), gender: new FormControl (), terms: new FormControl ()) 

Bind FormGroup-modellen till DOM enligt följande: 

app / registrerings-formen / registrering-form.component.html

  
Bli Medlem
...

[formGroup] = "signupForm" berättar Angular som du vill associera denna blankett med FormGroup deklareras i komponentklassen. När Angular ser formControlName = "email", det kontrollerar en instans av FormControl med nyckelvärdet e-post inuti parent FormGroup. 

På samma sätt uppdatera de andra formulärelementen genom att lägga till en formControlName = "värde" attribut som vi just gjorde här.

För att se om allt fungerar som förväntat, lägg till följande efter formtaggen:

app / registrerings-formen / registrering-form.component.html

 

Formvärde signupForm.value | json

Formstatus signupForm.status | json

Rör på SignupForm egendom genom JsonPipe att göra modellen som JSON i webbläsaren. Det här är användbart för felsökning och loggning. Du bör se en JSON-utgång så här.

Det finns två saker att notera här:

  1. JSON passar inte precis strukturen hos användarmodellen som vi skapade tidigare. 
  2. De signupForm.status visar att status för formuläret är INFALT. Detta visar tydligt att Validators.required I fältet E-post kontroll fungerar som förväntat. 

Formmodellens struktur och datamodellen ska matcha. 

// Form modell "email": "", "pwd": "", "confirmPwd": "", "gender": "", "terms": false // Användarmodell "email" , "lösenord": "pwd": "", "confirmPwd": "",, "gender": "", "terms": false

För att få den hierarkiska strukturen hos datamodellen borde vi använda en kapslad FormGroup. Dessutom är det alltid en bra idé att ha relaterade formulärelement i en enda FormGroup. 

Nested FormGroup

Skapa en ny FormGroup för lösenordet.

app / registrerings-formen / signup-form.component.ts

 this.signupForm = new FormGroup (email: new FormControl (", Validators.required), lösenord: ny FormGroup (pwd: new FormControl (), confirmPwd: new FormControl ()) : ny FormControl ())

För att binda den nya formulärmodellen med DOM gör du följande ändringar:

app / registrerings-formen / registrering-form.component.html

 

formGroupName = "lösenord" utför bindningen för den kapslade FormGroup. Nu matchar formmodellens struktur våra krav.

Formulärvärde: "email": "", "lösenord": "pwd": null, "confirmPwd": null, "gender": null, "terms": null Formstatus "INVALID"

Därefter måste vi validera formkontrollerna.

Validerar formuläret

Vi har en enkel validering på plats för e-postkontrollen. Det är dock inte tillräckligt. Här är hela listan över våra krav på validering.

  • Alla formkontrollelement är nödvändig.
  • Inaktivera Skicka-knappen tills statusen för formuläret är Giltig.
  • E-postfältet bör strikt innehålla ett e-post-ID.
  • Lösenordsfältet ska ha en minsta längd av 8.

Den första är lätt. Lägg till Validator.required till alla FormControls i formulärmodellen. 

app / registrerings-formen / signup-form.component.ts 

 this.signupForm = new FormGroup (email: new FormControl (", Validators.required), lösenord: ny FormGroup (pwd: new FormControl (", Validators.required), confirmPwd: new FormControl (", Validators.required) ), kön: ny FormControl ("Validators.required), // requiredTrue så att fältet villkoren är ogiltigt endast om markerade villkor: nya FormControl (", Validators.requiredTrue))

Därefter inaktivera knappen medan formuläret är INFALT.

app / registrerings-formen / registrering-form.component.html

 

För att lägga till en begränsning på e-post kan du antingen använda standardvärdet Validators.email eller skapa en anpassad Validators.pattern () som anger regelbundna uttryck som nedan:

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

Använd MINLENGTH validator för lösenordsfälten.

 lösenord: ny FormGroup (pwd: new FormControl (", [Validators.required, Validators.minLength (8)]), confirmPwd: new FormControl (", [Validators.required, Validators.minLength (8)],

Det är det för validering. Formmodellens logik verkar dock rotig och repetitiv. Låt oss städa upp det först. 

Refactoring koden med FormBuilder

Angular ger dig ett syntaksocker för att skapa nya instanser av FormGroup och FormControl som heter FormBuilder. FormBuilder API gör inget annat än vad vi har täckt här.

Det förenklar vår kod och gör processen att bygga en form lätt på ögonen. För att skapa en FormBuilder måste du importera den till signup-form.component.ts och injicera FormBuilder i konstruktören.

app / registrerings-formen / signup-form.component.ts 

importera FormBuilder, FormGroup, Validators från '@ vinkel / former'; ... exportklass SignupFormComponent implementerar OnInit signupForm: FormGroup; // Förklara signupForm // Injicera formbyggaren i konstruktörkonstruktorn (privat fb: FormBuilder)  ngOnInit () ...

I stället för att skapa en ny FormGroup (), vi använder this.fb.group att bygga en blankett Med undantag för syntaxen är allt annat detsamma.

app / registrerings-formen / signup-form.component.ts 

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

Visar valideringsfel 

För att visa fel ska jag använda det villkorliga direktivet ngIf på ett div-element. Låt oss börja med ingångskontrollfältet för e-post:

 

Det finns några problem här. 

  1. Var gjorde ogiltig och ren komma från? 
  2. signupForm.controls.email.invalid är för lång och djup.
  3. Felet säger inte uttryckligen varför det är ogiltigt.

För att svara på den första frågan har varje FormControl vissa egenskaper som ogiltig, giltig, ren, smutsig, rörd, och oberörd. Vi kan använda dessa för att avgöra om ett felmeddelande eller en varning ska visas eller inte. Bilden nedan beskriver i detalj alla dessa egenskaper.

Så div elementet med * ngIf kommer endast att göras om e-postmeddelandet är ogiltigt. Användaren kommer emellertid att hälsas med fel om inmatningsfälten är tomma även innan de har möjlighet att redigera formuläret. 

För att undvika detta scenario har vi lagt till det andra villkoret. Felet visas först efter kontrollen har besökts.

För att bli av med den långa kedjan av metodnamn (signupForm.controls.email.invalid), Kommer jag att lägga till ett par shorthand getter metoder. Detta gör dem mer tillgängliga och korta. 

app / registrerings-formen / signup-form.component.ts 

exportklass SignupFormComponent implementerar OnInit ... få email () returnera this.signupForm.get ('email');  få lösenord () returnera this.signupForm.get ('password');  få kön () returnera this.signupForm.get ('gender');  få villkor () returnera this.signupForm.get ('terms'); 

För att göra felet mer explicit har jag lagt till nestlösa villkor nedan:

app / registrerings-formen / registrering-form.component.html

 
E-postfältet kan inte vara tomt
E-post-id verkar inte rätt

Vi använder email.errors för att kontrollera alla möjliga valideringsfel och sedan visa dem tillbaka till användaren i form av anpassade meddelanden. Följ nu samma procedur för övriga formulärelement. Så här har jag kodat validering för lösenorden och villkoren för ingångskontroll.

app / registrerings-formen / registrering-form.component.html

  
Lösenordet måste vara mer än 8 tecken
...
Vänligen acceptera Villkoren först.

Skicka formuläret med ngSubmit

Vi är nästan färdiga med formuläret. Det saknar inlämningsfunktionen, som vi nu ska genomföra.

På formuläret skickas formmodellvärdena till komponentens användaregenskap.

app / registrerings-formen / signup-form.component.ts

public onFormSubmit () if (this.signupForm.valid) this.user = this.signupForm.value; console.log (this.user); / * Alla API-logg via tjänster går här * /

Wrapping It Up

Om du har följt denna tutorial-serie från början hade vi en praktisk erfarenhet av två populära formbyggnadsteknologier i Angular. De mallstyrda och modellstyrda teknikerna är två sätt att uppnå samma sak. Personligen föredrar jag att använda de reaktiva formerna av följande skäl:

  • Alla formulärvalideringslogik kommer att ligga på en enda plats - inuti din komponentklass. Det här är väldigt produktivt än mallen, där ngModel-direktiven sprids över mallen.
  • Till skillnad från mallstyrda former är modelldrivna former lättare att testa. Du behöver inte tillgripa testbiblioteken i slutet för att testa din blankett.
  • Valideringslogiken går in i komponentklassen och inte i mallen.
  • För ett formulär med ett stort antal formelement har detta tillvägagångssätt något som heter FormBuilder för att underlätta skapandet av FormControl-objekt.

Vi saknade en sak, och det skriver en validerare för felaktigheten för lösenordet. I den sista delen av serien kommer vi att täcka allt du behöver veta om att skapa anpassade valideringsfunktioner i Angular. Stanna stilla tills dess.

Under tiden finns det gott om ramar och bibliotek för att hålla dig upptagen, med många saker på Envato Market att läsa, studera och använda.