Introduktion till formulär i vinkel 4 Skriva anpassade formulärvaliderare

Detta är den tredje delen av serien om att skapa former i Angular. I de två första handledningarna använde vi Angular's mall-driven och modelldriven metod för att skapa former. Men när vi redogjorde för båda tillvägagångssätten fanns det något som vi inte täckte - anpassade valideringsfunktioner. Denna handledning kommer att täcka allt du behöver veta om att skriva anpassade validatorer som uppfyller dina krav.

förutsättningar

Du behöver inte ha följt del en eller två i den här serien för att del tre ska vara meningsfull. Men om du är helt ny på formulär i Angular, bör du gå vidare till den första handledningen i den här serien och börja därifrån. 

I annat fall ta en kopia av den här koden från vårt GitHub repo och använd det som utgångspunkt.  

Inbyggda validatorer

Angular pryder inte ett stort inbyggt validatorbibliotek. Från Angular 4 har vi följande populära validatorer i Angular:

  • nödvändig
  • minlength
  • Maxlängd
  • mönster

Det finns faktiskt några fler, och du kan se hela listan i Angular docs. 

Vi kan använda ovanstående inbyggda validatorer på två sätt:

1. Som direktiv i mallstyrda former.

2. Som validerare inne i FormControl konstruktör i modelldrivna former.

name = new FormControl (", Validators.required) 

Om ovanstående syntax inte är meningsfull följer du mina tidigare handledning om att skapa en registreringsblankett med hjälp av en mallstyrd strategi eller modelldriven strategi och sedan släppa tillbaka!

De inbyggda formulärvaliderna täcker knappast alla valideringsanvändningsfall som kan krävas i en verklig applikation. Till exempel kan en anmälningsblankett behöva kontrollera om värdena på lösenordet och bekräfta lösenordskontrollfälten är lika och visa ett felmeddelande om de inte matchar. En validator som svartlistor e-postmeddelanden från en viss domän är ett annat vanligt exempel. 

Här är ett faktum: Mallformade former är bara modelldrivna former under. I en mallstyrd form låter vi mallen ta hand om modellskapandet för oss. Den uppenbara frågan nu är hur du bifogar en validerare till en blankett?

Validatorer är bara funktioner. I en modelldriven form är bifogade validatorer till FormControl okomplicerad. I en mallstyrd form är det emellertid lite mer arbete att göra. Förutom valideringsfunktionen måste du skriva ett direktiv för validatorn och skapa instanser av direktivet i mallen.

Dykning i detaljerna

Även om detta redan har blivit täckt, kommer vi att gå igenom ett snabbt sammanfattning av koden för registreringsformuläret. Först här är det reaktiva tillvägagångssättet.

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

 // Använd formverktyget för att bygga formulärmodellen 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 )], bekräftaPwd: [", [Validators.required, Validators.minLength (8)]], validator: PasswordMatch), kön: [" Validators.required ",)

FormBuilder är ett syntaksocker som skapar FormGroup och FormControl instanser. en FormControl spårar värdet och valideringsstatusen för ett enskilt formelement. en FormGroup, å andra sidan innefattar en grupp av FormControl instanser, och det spårar värdet och giltigheten för hela gruppen.

Här är den struktur som vi har följt:

FormGroup -> 'signupForm' FormControl -> 'email' FormGroup -> 'lösenord' FormControl -> 'pwd' FormControl -> 'confirmPwd' FormControl -> 'gender' 

Beroende på kraven kan vi bifoga en validator till en FormControl eller a FormGroup. En blacklisting-validator för e-post skulle kräva att den bifogas FormControl Exempel på e-postmeddelandet. 

För mer komplicerade valideringar där flera kontrollfält måste jämföras och valideras är det emellertid en bättre idé att lägga till valideringslogiken till föräldern FormGroup. Som du kan se, Lösenord har en FormGroup av sig själv, och det gör det lätt för oss att skriva validatorer som kontrollerar jämlikheten hos pwd och confirmPwd.

För den mallstyrda formen går all den logiken in i HTML-mallen, och här är ett exempel:

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

...

ngModel skapar en förekomst av FormControl och binder den till ett formkontrollelement. Liknande, ngModelGroup skapar och binder en FormGroup instans till ett DOM-element. De delar samma modelldomänstruktur som diskuterats ovan. 

Det är också intressant att notera det FormControl, FormGroup, och FormArray utöka AbstractControl klass. Vad det här betyder är att AbstractControl klassen ansvarar för att spåra värdena på formobjekt, validera dem och driva andra saker som orörda, smutsiga och rörda metoder. 

Nu när vi är bekanta med både formteknikerna, låt oss skriva vår första anpassade validator.

Anpassad valideringsfunktion för modelldrivna former

Validatorer är funktioner som tar en FormControl/FormGroup Exempel som inmatning och retur heller null eller ett felobjekt. null returneras när valideringen är framgångsrik, och om inte, kastas felobjektet. Här är en mycket grundläggande version av en valideringsfunktion. 

app / lösenord-match.ts

importera FormGroup från '@ vinkel / formulär'; exportfunktion passwordMatch (kontroll: FormGroup): [nyckel: sträng]: boolean 

Jag har förklarat en funktion som accepterar en förekomst av FormGroup som en inmatning. Det returnerar ett objekt med en nyckel av typsträng och ett sannt / falskt värde. Det här är så att vi kan returnera ett felobjekt av formuläret nedan:

mismatch: true

Därefter måste vi få värdet av pwd och confirmPwd FormControl instanser. Jag ska använda control.get () att hämta sina värden. 

exportfunktion passwordMatch (kontroll: FormGroup): [nyckel: sträng]: boolean // Hämta pwd och confirmPwd med control.get const pwd = control.get ('pwd'); const confirmPwd = control.get ('confirmPwd');  

Nu behöver vi göra jämförelsen och returnera antingen null eller ett felobjekt.

app / lösenord-match.ts

importera AbstractControl från '@ vinkel / former'; exportfunktion passwordMatch (kontroll: AbstractControl): [nyckel: sträng]: boolean // Hämta pwd och confirmPwd med control.get const pwd = control.get ('pwd'); const confirmPwd = control.get ('confirmPwd'); // Om FormControl-objekt inte existerar, returnera null om (! Pwd ||! ConfirmPwd) returnera null; // Om de verkligen är lika, returnera null om (pwd.value === confirmPwd.value) return null;  // Else returnerar falsk retur mismatch: true;  

Varför ersatte jag? FormGroup med AbstractControl? Som du vet, AbstractControl är moder till alla Form * -klasser, och det ger dig mer kontroll över formkontrollobjekten. Det har den extra fördelen att det gör vår valideringskod mer konsekvent.

Importera passwordMatch funktion i SignupForm komponent och förklara det som en validator för lösenordet FormGroup exempel.

app / lösenord-match.ts

importera passwordMatch från './.../password-match'; ... exportklass SignupFormComponent implementerar OnInit ngOnInit () // Använd formverktyget för att bygga formulärmodellen this.signupForm = this.fb.group (... lösenord: this.fb.group (pwd: [", [Validators.required, Validators.minLength (8)]], bekräftaPwd: [", [Validators.required, Validators.minLength (8)]], validator: passwordMatch ), ...) 

Visar fel

Om du gjorde allt rätt, password.errors? .mismatch kommer att vara sant när värdena för båda fälten inte matchar.

password.errors? .mismatch json

Även om det finns alternativa sätt att visa fel, kommer jag att använda ngIf direktivet för att avgöra om ett felmeddelande ska visas eller inte.

Först ska jag använda ngIf för att se om lösenordet är ogiltigt. 

  

Vi använder password.touched för att säkerställa att användaren inte hälsas med fel redan innan en tangent har tryckts.

Därefter kommer jag att använda ngIf = "expression, sedan en annan b" -syntax för att visa rätt fel.

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

    Lösenordet matchar inte   Lösenordet måste vara mer än 8 tecken 

Där har du det, en arbetsmodell av validatorn som kontrollerar lösenordslikhet.

Demo för anpassade validatorer i modelldriven form

Anpassat valideringsdirektiv för malldriven form

Vi använder samma validatorfunktion som vi skapade för den modelldrivna formen tidigare. Vi har emellertid inte direkt tillgång till instanser av FormControl/FormGroup i en mall-driven form. Här är de saker du behöver göra för att validatorn ska fungera:

  1. Skapa en PasswordMatchDirective som fungerar som ett omslag runt passwordMatch valideringsfunktion. Vi kommer att registrera direktivet som en validator med hjälp av NG_VALIDATORS leverantören. Mer om detta senare.
  2. Fäst direktivet till mallformskontrollen. 

Låt oss skriva direktivet först. Så här ser ett direktiv ut i Angular:

app / lösenord-match.ts

importera AbstractControl från '@ vinkel / former'; exportfunktion passwordMatch (kontroll: AbstractControl): [nyckel: sträng]: boolean // Hämta pwd och confirmPwd med control.get const pwd = control.get ('pwd'); const confirmPwd = control.get ('confirmPwd'); // Om FormControl-objekt inte existerar, returnera null om (! Pwd ||! ConfirmPwd) returnera null; // Om de verkligen är lika, returnera null om (pwd.value === confirmPwd.value) return null;  // Else returnerar falsk retur mismatch: true;  // PasswordMatchDirective @Directive (selector: ", leverantörer: []) export klass PasswordMatchDirective 

De @Direktiv dekoratorn används för att markera klassen som ett vinkeldirektiv. Den accepterar ett objekt som ett argument som anger direktivets konfigurationsmetodata, såsom valörer för vilka direktivet ska bifogas, och listan över leverantörer som ska injiceras etc. Låt oss fylla i direktivet metadata:

app / lösenord-match.ts

@Directive (selector: '[passwordMatch] [ngModelGroup]', // 1 leverantörer: [// 2 provide: NG_VALIDATORS, useValue: passwordMatch, multi: true]) exportklass PasswordMatchDirective 
  1. Direktivet är nu kopplat till alla inmatningskontroller som har attributen ngModelGroup och passwordMatch
  2. Vi utökar de inbyggda validatorerna med hjälp av NG_VALIDATORS leverantören. Som tidigare nämnt, NG_VALIDATORS är en leverantör som har en omfattande samling av validatorer. De passwordMatch Funktionen som vi skapade tidigare förklaras som ett beroende. De multi: true sätter denna leverantör till en multi-leverantör. Vad detta innebär är att vi kommer att lägga till den befintliga samlingen av validatorer som tillhandahålls av NG_VALIDATORS.

Lägg nu till direktivet i deklarationsmatrisen ngModule.

app / app.module.ts

... importera PasswordMatchDirective från './password-match'; @NgModule (deklarationer: [AppComponent, SignupFormComponent, PasswordMatchDirective], import: [BrowserModule, FormsModule], leverantörer: [], bootstrap: [AppComponent]) exportklass AppModule  

Visar felmeddelanden

För att visa valideringsfelmeddelandena ska jag använda samma mall som vi skapade för de modelldrivna formulären.

 
Lösenordet matchar inte Lösenordet måste vara mer än 8 tecken

Slutsats

I denna handledning lärde vi oss om att skapa anpassade vinkelvaliderare för formulär i Angular. 

Validatorer är funktioner som returnerar null eller ett felobjekt. I modelldrivna former måste vi bifoga validatorn till en FormControl / FormGroup-förekomst, och det är det. Förfarandet var lite mer komplext i en mallstyrd form eftersom vi behövde skapa ett direktiv ovanpå valideringsfunktionen. 

Om du är intresserad av att fortsätta lära dig mer om JavaScript, kom ihåg att kolla vad vi har på Envato Market.

Jag hoppas att du har haft den här serien på Forms in Angular. Jag skulle gärna höra dina tankar. Dela dem genom kommentarerna.