Introduksjon til skjemaer i vinkel 4 Reaktive skjemaer

Hva du skal skape

Dette er den andre delen av serien om Introduksjon til skjemaer i vinkel 4. I den første delen opprettet vi et skjema ved hjelp av mal-drevet tilnærming. Vi brukte direktiver som ngModel, ngModelGroup og ngForm å overlaste formelementene. I denne opplæringen tar vi en annen tilnærming til å bygge former - den reaktive måten. 

Reaktive skjemaer

Reaktive skjemaer tar en annen tilnærming enn i de malformede skjemaene. Her lager og initialiserer vi danner kontrollobjekter i vår komponent klasse. De er mellomliggende objekter som holder tilstanden til skjemaet. Vi vil da binde dem til danner kontrollelementer i malen.

Formkontrollobjektet lytter til enhver endring i inngangskontrollverdiene, og de reflekteres umiddelbart i objektets tilstand. Siden komponenten har direkte tilgang til datamodellstrukturen, kan alle endringer synkroniseres mellom datamodellen, skjemakontrollobjektet og inngangskontrollverdiene. 

Praktisk sett, hvis vi bygger et skjema for oppdatering av brukerprofilen, er datamodellen brukerobjektet hentet fra serveren. Ved konvensjon lagres dette ofte inne i komponentens brukeregenskaper (this.user). Skjemakontrollobjektet eller skjemamodellen vil være bundet til malens faktiske formelementer.

Begge disse modellene skal ha lignende strukturer, selv om de ikke er identiske. Imidlertid bør inngangsverdiene ikke direkte strømme inn i datamodellen. Bildet beskriver hvordan brukerinngangen fra malen gjør veien til skjemamodellen.

La oss komme i gang.

Forutsetninger

Du trenger ikke å ha fulgt del 1 av denne serien, for del to er fornuftig. Men hvis du er ny på skjemaer i Angular, vil jeg anbefale å gå gjennom den template-drevne strategien. Koden for dette prosjektet er tilgjengelig på mitt GitHub-depot. Sørg for at du er på den rette grenen og last ned zip-en, eller, klone repoen for å se skjemaet i aksjon. 

Hvis du foretrekker å starte fra scratch i stedet, må du sørge for at du har installert Angular CLI. Bruke ng kommandoen for å generere et nytt prosjekt. 

$ ng ny SignupFormProject

Deretter genererer du en ny komponent for SignupForm eller opprett en manuelt. 

ng generere komponent SignupForm

Erstatt innholdet i app.component.html med dette:

 

Her er katalogstrukturen for src / katalogen. Jeg har fjernet noen ikke-essensielle filer for å holde ting enkelt.

. ├── 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 

Som du kan se, en katalog for SignupForm komponenten er opprettet automatisk. Det er der det meste av koden vår vil gå. Jeg har også laget en ny User.ts for lagring av brukermodellen vår.

HTML-malen

Før vi dykker inn i selve komponentmalen, må vi ha en abstrakt ide om hva vi bygger. Så her er formstrukturen som jeg har i tankene mine. Påmeldingsskjemaet vil ha flere inntastingsfelter, et valgelement og et avkrysningselement. 


Her er HTML-malen som vi skal bruke til vår registreringsside. 

HTML-mal

 
Melde deg på

CSS-klassene som brukes i HTML-malen, er en del av Bootstrap-biblioteket som brukes til å gjøre ting pent. Siden dette er ikke en designopplæring, snakker jeg ikke mye om CSS-aspektene av skjemaet med mindre det er nødvendig. 

Grunnleggende formoppsett

For å opprette et Reactive-skjema må du importere ReactiveFormsModule fra @ vinkel / former og legg det til importarmen i app.module.ts.

app / app.module.ts

// Import ReactiveFormsModule import ReactiveFormsModule fra '@ vinkel / former'; @NgModule (... // Legg til modulen til importen Array import: [BrowserModule, ReactiveFormsModule ...) eksport klasse AppModule  

Deretter oppretter du en brukermodell for registreringsskjemaet. Vi kan enten bruke en klasse eller et grensesnitt for å lage modellen. For denne opplæringen skal jeg eksportere en klasse med følgende egenskaper.

app / User.ts

Eksportklasse Bruker id: nummer; e-post: streng; // Begge passordene er i et enkelt objektpassord: pwd: string; confirmPwd: string; ; kjønn: streng; vilkår: boolsk; konstruktør (verdier: Objekt = ) // Konstruktørinitialisering Object.assign (dette, verdier);  

Nå opprett en forekomst av brukermodellen i SignupForm komponent. 

app / registrering-skjema / registrerings-form.component.ts

importer Komponent, OnInit fra '@ vinkel / kjerne'; // Importer brukermodellimporten Bruker fra './.../User'; @Component (selector: 'app-signup-form', templateUrl: './signup-form.component.html', styleUrls: ['./signup-form.component.css']) eksportklasse SignupFormComponent implementerer OnInit // Kjønnsliste for velg kontrollelementet privat kjønnListe: streng []; // Egenskap for brukerens private bruker: Bruker; ngOnInit () this.genderList = ['Male', 'Female', 'Others']; 

For registrering-form.component.html fil, jeg skal bruke den samme HTML-malen diskutert ovenfor, men med mindre endringer. Påmeldingsskjemaet har et velgfelt med en liste over alternativer. Selv om det fungerer, vil vi gjøre det på den kantede måten ved å løpe gjennom listen ved hjelp av ngFor direktiv.

app / registrering-skjema / registrering-form.component.html

Melde deg på...
...

Merk: Du kan få en feil som sier Ingen leverandør for ControlContainer. Feilen vises når en komponent har en

tag uten et formGroup-direktiv. Feilen forsvinner når vi legger til et FormGroup-direktiv senere i opplæringen.

Vi har en komponent, en modell og en skjemamal for hånden. Hva nå? Det er på tide å få hendene skitne og bli kjent med APIene du trenger for å lage reaktive skjemaer. Dette inkluderer FormControl og FormGroup

Spore staten ved hjelp av FormControl

Mens du bygger formularer med den reaktive formstrategien, vil du ikke komme over ngModel og ngForm-direktiver. I stedet bruker vi den underliggende FormControl and FormGroup API.

En FormControl er et direktiv som brukes til å opprette et FormControl-eksempel som du kan bruke til å holde oversikt over et bestemt formelements tilstand og dens valideringsstatus. Slik fungerer FormControl:

/ * Import FormControl først * / import FormControl fra '@ vinkel / former'; / * Eksempel på å opprette en ny FormControl-forekomst * / eksportklasse SignupFormComponent email = new FormControl (); 

e-post er nå et FormControl-eksempel, og du kan knytte det til et input-kontrollelement i malen din som følger:

Melde deg på

Mallformelementet er nå bundet til FormControl-forekomsten i komponenten. Hva det betyr er en endring i inngangskontrollverdien blir reflektert i den andre enden. 

En FormControl-konstruktør aksepterer tre argumenter - en innledende verdi, en rekke synkroniseringsvaliderere, og en rekke asynk-validatorer - og som du kanskje har gjettet, er de alle valgfrie. Vi vil dekke de to første argumentene her. 

importere Validators fra '@ vinkel / former'; ... / * FormControl med innledende verdi og en validator * / email = new FormControl ('[email protected] ', Validators.required); 

Angular har et begrenset sett med innebygde validatorer. Den populære valideringsmetoden inkluderer Validators.required, Validators.minLength, Validators.maxlength, og Validators.pattern. Men for å bruke dem må du først importere Validator API.

For vårt registreringsskjema har vi flere inngangskontrollfelt (for e-post og passord), et velg felt og et avkrysningsfelt. Snarere enn å skape individuelle FormControl objekter, ville det ikke være mer fornuftig å gruppere alle disse FormControls under en enkelt enhet? Dette er gunstig fordi vi nå kan spore verdien og gyldigheten av alle sub-FormControl-objektene på ett sted. Det er hva FormGroup er for. Så vi registrerer en foreldre FormGroup med flere barn FormControls. 

Grupper flere FormControls med FormGroup

For å legge til en FormGroup, importerer du først. Deretter erklærer du signupForm som en klasseegenskap og initialiserer den som følger:

app / registrering-skjema / registrerings-form.component.ts

// Importer API for å bygge en formular import FormControl, FormGroup, Validators fra '@ vinkel / former'; eksportklasse SignupFormComponent implementerer OnInit genderList: String []; signupForm: FormGroup; ... ngOnInit () this.genderList = ['Male', 'Female', 'Others']; this.signupForm = new FormGroup (email: new FormControl ("Validators.required), pwd: new FormControl (), confirmPwd: new FormControl (), kjønn: new FormControl (), vilkår: new FormControl ()) 

Bind FormGroup-modellen til DOM som følger: 

app / registrering-skjema / registrering-form.component.html

  
Melde deg på
...

[formGroup] = "signupForm" forteller Angular som du vil knytte dette skjemaet til FormGroup erklært i komponentklassen. Når Angular ser formControlName = "email", det kontrollerer for en forekomst av FormControl med nøkkelverdien e-post inne i foreldreformgruppen. 

På samme måte oppdaterer du de andre formelementene ved å legge til en formControlName = "verdi" attributt som vi nettopp gjorde her.

For å se om alt fungerer som forventet, legg til følgende etter skjemaetiketten:

app / registrering-skjema / registrering-form.component.html

 

Form verdi signupForm.value | json

Skjema status signupForm.status | json

Rør på SignupForm eiendom gjennom JsonPipe å gjengi modellen som JSON i nettleseren. Dette er nyttig for feilsøking og logging. Du bør se en JSON-utgang som denne.

Det er to ting å merke seg her:

  1. JSON samsvarer ikke nøyaktig med strukturen til brukermodellen som vi opprettet tidligere. 
  2. De signupForm.status viser at status for skjemaet er UAKTLIG. Dette viser tydelig at Validators.required På kontrollpanelet for e-post fungerer det som forventet. 

Strukturen til formmodellen og datamodellen bør samsvare. 

// Form modell "email": "", "pwd": "", "confirmPwd": "", "gender": "", "terms": false // Brukermodell "email" , "passord": "pwd": "", "confirmPwd": "",, "kjønn": "", "vilkår"

For å få den hierarkiske strukturen til datamodellen, bør vi bruke en nestet FormGroup. I tillegg er det alltid en god ide å ha relaterte formelementer under en enkelt FormGroup. 

Nested FormGroup

Opprett en ny FormGroup for passordet.

app / registrering-skjema / registrerings-form.component.ts

 this.signupForm = new FormGroup (email: new FormControl ("Validators.required), passord: ny FormGroup (pwd: new FormControl (), confirmPwd: new FormControl ()) : Ny FormControl ())

Nå, for å binde den nye skjemamodellen med DOM, gjør du følgende endringer:

app / registrering-skjema / registrering-form.component.html

 

formGroupName = "passord" utfører bindingen for den nestede FormGroup. Nå stemmer strukturen på skjemamodellen med våre krav.

Formularverdi: "email": "", "passord": "pwd": null, "confirmPwd": null, "kjønn": null, "vilkår": null Skjema status "INVALID"

Deretter må vi validere skjemakontrollene.

Validerer skjemaet

Vi har en enkel validering på plass for e-postkontrollen. Det er imidlertid ikke tilstrekkelig. Her er hele listen over våre krav til validering.

  • Alle formelementer er nødvendig.
  • Deaktiver innsendingsknappen til statusen på skjemaet er Gyldig.
  • E-postfeltet bør strengt inneholde et e-post-ID.
  • Passordfeltet skal ha en minimumslengde på 8.

Den første er lett. Legg til Validator.required til alle FormControls i skjema modellen. 

app / registrering-skjema / registrerings-form.component.ts 

 this.signupForm = new FormGroup (email: new FormControl (", Validators.required), passord: ny FormGroup (pwd: new FormControl (", Validators.required), confirmPwd: new FormControl (", Validators.required) ), kjønn: ny FormControl ("Validators.required), // requiredTrue slik at vilkårene er ugyldige bare hvis de er merket: New FormControl (", Validators.requiredTrue))

Deretter deaktiverer du knappen mens skjemaet er UAKTIVT.

app / registrering-skjema / registrering-form.component.html

 

For å legge til en begrensning på e-post, kan du enten bruke standardinnstillingen Validators.email eller lag en egendefinert Validators.pattern () som spesifiserer vanlige uttrykk som den nedenfor:

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

Bruke MINLENGTH validator for passordfeltene.

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

Det er det for validering. Formelmodelllogikken virker imidlertid rotete og repeterende. La oss rydde det opp først. 

Refactoring koden ved hjelp av FormBuilder

Angular gir deg en syntaks sukker for å lage nye forekomster av FormGroup og FormControl kalt FormBuilder. FormBuilder API gjør ikke noe spesielt annet enn det vi har dekket her.

Det forenkler koden vår og gjør prosessen med å bygge et skjema lett på øynene. For å opprette en FormBuilder må du importere den til signup-form.component.ts og injiser FormBuilder i konstruktøren.

app / registrering-skjema / registrerings-form.component.ts 

importere FormBuilder, FormGroup, Validators fra '@ vinkel / former'; ... eksportklasse SignupFormComponent implementerer OnInit signupForm: FormGroup; // Forklar signupForm // Injiser formbyggeren i konstruktørkonstruktøren (privat fb: FormBuilder)  ngOnInit () ...

I stedet for å lage en ny FormGroup (), vi bruker this.fb.group å bygge et skjema. Bortsett fra syntaksen, forblir alt annet det samme.

app / registrering-skjema / registrerings-form.component.ts 

 ngOnInit () ... this.signupForm = this.fb.group (email: [", [Validators.required, Validators.pattern ('[a-z0-9 ._% + -] + @ [a-z0- 9 .-] + \. [Az] 2,3 $ ')]], passord: this.fb.group (pwd: [", [Validators.required, Validators.minLength (8)]], confirmPwd : [", [Validators.required, Validators.minLength (8)]]), kjønn: [", Validators.required], vilkår: [", Validators.requiredTrue])

Vise valideringsfeil 

For å vise feilene, skal jeg bruke betingelsesdirektivet ngIf på et div-element. La oss starte med inngangskontrollfeltet for e-post:

 

Det er et par problemer her. 

  1. Hvor gjorde ugyldig og uberørte kommer fra? 
  2. signupForm.controls.email.invalid er for lang og dyp.
  3. Feilen sier ikke eksplisitt hvorfor den er ugyldig.

For å svare på det første spørsmålet, har hver FormControl visse egenskaper som ugyldig, gyldig, uberørte, skitten, berørt, og urørt. Vi kan bruke disse for å avgjøre om en feilmelding eller en advarsel skal vises eller ikke. Bildet nedenfor beskriver hver av disse egenskapene i detalj.

Så div elementet med * ngIf vil bli gjengitt bare hvis e-postadressen er ugyldig. Brukeren vil imidlertid bli møtt med feil om at inntastingsfeltene er tomme selv før de har mulighet til å redigere skjemaet. 

For å unngå dette scenarioet har vi lagt til den andre betingelsen. Feilen vil bli vist bare etter kontrollen har blitt besøkt.

For å bli kvitt den lange kjeden av metodenavn (signupForm.controls.email.invalid), Jeg skal legge til et par shorthand getter metoder. Dette gjør dem mer tilgjengelige og korte. 

app / registrering-skjema / registrerings-form.component.ts 

eksportklasse SignupFormComponent implementerer OnInit ... få email () return this.signupForm.get ('email');  få passord () return this.signupForm.get ('passord');  få kjønn () return this.signupForm.get ('gender');  få vilkår () return this.signupForm.get ('terms'); 

For å gjøre feilen mer eksplisitt, har jeg lagt til nestede ngIf-forhold nedenfor:

app / registrering-skjema / registrering-form.component.html

 
E-postfeltet kan ikke være tomt
E-post-ID-en virker ikke riktig

Vi bruker email.errors for å sjekke alle mulige valideringsfeil og deretter vise dem tilbake til brukeren i form av egendefinerte meldinger. Følg nå samme fremgangsmåte for de andre skjemaelementene. Slik har jeg kodet valideringen for passordene og betingelsene inngangskontroll.

app / registrering-skjema / registrering-form.component.html

  
Passordet må være over 8 tegn
...
Vennligst godta vilkårene først.

Send inn skjemaet ved hjelp av ngSubmit

Vi er nesten ferdige med skjemaet. Det mangler innsendingsfunksjonaliteten, som vi skal implementere nå.

På skjemaet sendes formmodellverdiene til komponentens brukeregenskap.

app / registrering-skjema / registrerings-form.component.ts

offentlig onFormSubmit () if (this.signupForm.valid) this.user = this.signupForm.value; console.log (this.user); / * Eventuelle API-anropslogikk via tjenester går her * /

Pakke det opp

Hvis du har fulgt denne opplæringsserien fra starten, hadde vi en praktisk erfaring med to populære formbyggingsteknologier i Angular. De mal-drevne og modelldrevne teknikkene er to måter å oppnå det samme. Personlig foretrekker jeg å bruke de reaktive skjemaene av følgende grunner:

  • Alle skjema validering logikken vil være plassert på et enkelt sted-inne i komponent klassen. Dette er langt mer produktivt enn malmetoden, der ngModel-direktiver er spredt over malen.
  • I motsetning til mal-drevne skjemaer, er modelldrevne skjema lettere å teste. Du trenger ikke å ty til slutt-til-ende testbibliotek for å teste skjemaet ditt.
  • Validasjonslogikken vil gå inn i komponentklassen og ikke i malen.
  • For et skjema med et stort antall formelementer, har denne tilnærmingen noe som heter FormBuilder for å gjøre opprettelsen av FormControl-objekter lettere.

Vi har gått glipp av en ting, og det er å skrive en validator for feilmatchingen av passordet. I den siste delen av serien vil vi dekke alt du trenger å vite om å skape egendefinerte valideringsfunksjoner i Angular. Hold deg innstilt til da.

I mellomtiden er det nok av rammer og biblioteker å holde deg opptatt, med mange ting på Envato Market å lese, studere og bruke.