Angular 4 forms are of two types. Template driven and Reactive. In our previous article, we have discussed and worked with the form validation in Template Driven Angular Forms. Now we are going to see how to implement validation in the Reactive Forms. Reactive forms come as the best choice to build complex forms. Template driven forms are built with ng-model, we write the logic part, validation part, and controls in the template. But in the reactive forms, the logic and validation part is in the controller, i.e we define the model of the form in the component. This gives us more control over the form values and validations when compared to template-driven forms.

Reactive Forms are also known as “model-driven” forms because they use the reactive model-driven technique to handle the form data which explicitly manages the form data between component and template. It manages the form data flow between the non-UI data model and UI-oriented form model and returns the states and values of HTML form controls. And it provides reactive patterns and validation to handle forms.

Reactive forms use the form control objects to manipulate the forms. So you can create form tree using form control objects into the component and bind form controls to the component template using formControlName. So once you define the form control objects in component class it will immediately access data model and the form control structure. Then, you can push data and handle the data values. The component can observe the changes in form control state and react to those changes.

Now let us start learning Reactive Forms with the use of the same example which we have used in the previous article.

<form> <div> <label>Name</label> <input type="text" name="name"/> </div> <div> <label>Email</label> <input type="email" name="email"/> </div> <div> <label>Age</label> <input type="text" name="age"/> </div> <div> <h3>Address</h3> <div> <label>City</label> <input type="text" name="city"/> </div> <div> <label>Country</label> <input type="text" name="country"/> </div> </div> <button type="submit">Submit</button> </form> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 < form > < div > < label > Name < / label > < input type = "text" name = "name" / > < / div > < div > < label > Email < / label > < input type = "email" name = "email" / > < / div > < div > < label > Age < / label > < input type = "text" name = "age" / > < / div > < div > < h3 > Address < / h3 > < div > < label > City < / label > < input type = "text" name = "city" / > < / div > < div > < label > Country < / label > < input type = "text" name = "country" / > < / div > < / div > < button type = "submit" > Submit < / button > < / form >

The requirements for the above form is as follows:

Name : A unique name should be provided. Email : A valid email id that is unique is required. Age : Age must be a number between 18 and 85. Country : A valid country name is required.

Once we fill all the fields with valid inputs, the submit button will be enabled. On click, it will submit the form.

In the next steps, we will learn to implement the above specifications

Before we start using Reactive Forms we have to import the ReactiveFormsModule class into app.module.js like below:

import {ReactiveFormsModule} from '@angular/forms' import {NgModule} from '@angular/core' import {BrowserModule} from '@angular/platform-browser' import {AppComponent} from 'src/app.component'; @NgModule({ imports: [ BrowserModule, ReactiveFormsModule ], declarations: [ AppComponent], bootstrap: [ AppComponent ] }) export class AppModule {} 1 2 3 4 5 6 7 8 9 10 import { ReactiveFormsModule } from '@angular/forms' import { NgModule } from '@angular/core' import { BrowserModule } from '@angular/platform-browser' import { AppComponent } from 'src/app.component' ; @ NgModule ( { imports : [ BrowserModule , ReactiveFormsModule ] , declarations : [ AppComponent ] , bootstrap : [ AppComponent ] } ) export class AppModule { }

And never forgot to import BrowserModule for bootstraping the application.

Now let us begin with HTML Form element which we used in our previous article. While using the Reactive Forms we shouldn’t import FormsModule (Make sure the FormsModule is not imported). For creating Reactive Form controls you must import FormGroup and FormControl in your AppComponent like below.

import { FormGroup, FormControl, FormArray, NgForm } from '@angular/forms'; 1 import { FormGroup , FormControl , FormArray , NgForm } from '@angular/forms' ;

Each form element should be an object of FormControl.

import { FormGroup, FormControl, FormArray, NgForm } from '@angular/forms'; // Importing Reactive Form related classes. import { Component, OnInit } from '@angular/core' @Component({ selector: 'my-app', templateUrl: 'src/app.component.html' }) export class AppComponent implements OnInit { private sampleForm: FormGroup; constructor() { } ngOnInit() { // Each will create new Form control. this.sampleForm = new FormGroup({ 'name': new FormControl(), 'email': new FormControl(), 'age': new FormControl(), 'birthYear': new FormControl(), 'address': new FormGroup({ 'country': new FormControl(), 'city': new FormControl() }) }); } printSampleForm() { console.log(this.sampleForm); } onSubmit(sampleForm: NgForm) { console.log('Form successful submit.'); console.log(sampleForm.value); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import { FormGroup , FormControl , FormArray , NgForm } from '@angular/forms' ; // Importing Reactive Form related classes. import { Component , OnInit } from '@angular/core' @ Component ( { selector : 'my-app' , templateUrl : 'src/app.component.html' } ) export class AppComponent implements OnInit { private sampleForm : FormGroup ; constructor ( ) { } ngOnInit ( ) { // Each will create new Form control. this . sampleForm = new FormGroup ( { 'name' : new FormControl ( ) , 'email' : new FormControl ( ) , 'age' : new FormControl ( ) , 'birthYear' : new FormControl ( ) , 'address' : new FormGroup ( { 'country' : new FormControl ( ) , 'city' : new FormControl ( ) } ) } ) ; } printSampleForm ( ) { console . log ( this . sampleForm ) ; } onSubmit ( sampleForm : NgForm ) { console . log ( 'Form successful submit.' ) ; console . log ( sampleForm . value ) ; } }

In the above example, printSampleForm and onSubmit methods will handle the form submitted data. Here you can see we have used different classes like FormGroup, FormControl, and FormArray. All these are needed for creating the forms. First FormGroup is used to declare the Form and next FormControl is used to declare all form controls inside the FormGroup. FormArray in angular 4 is used to maintain the group of form controls.

When we are using those three classes we should concentrate on three arguments. First one is value , validators and the async validator.

constructor(value: any, validator?: ValidatorFn | ValidatorFn[], asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]); 1 constructor ( value : any , validator ? : ValidatorFn | ValidatorFn [ ] , asyncValidator ? : AsyncValidatorFn | AsyncValidatorFn [ ] ) ;

The value is different based on the objects.

FormGroup – here the value will be an object, each object element should be a FormControl.

FormArray – the value is an array. the array of form controls.

FormControl – the value is the string value or object, it is the default value of form control.

Now we have created FormGroup but our HTML is yet uninformed about the Form object use of formGroup, formControlName, formGroupName, and formArrayName directives. For this we will assign form object to HTML form like the following:

<form [formGroup]="sampleForm" (ngSubmit)="onSubmit(sampleForm)"> <div> <label>Name</label> <input type="text" name="name" formControlName="name"> </div> <div> <label>Email</label> <input type="text" name="email" formControlName="email"> </div> <div> <label>Age</label> <input type="text" name="age" formControlName="age"> </div> <div formGroupName="address"> <h3>Address</h3> <div> <label>Country</label> <input type="text" name="country" formControlName="country"> </div> <div> <label>City</label> <input type="text" name="city" formControlName="city"> </div> </div> <pre>{{sampleForm.value | json}}</pre> <button type="submit">Submit</button> <button type="button" (click)="printSampleForm()">Print to console</button> </form> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 < form [ formGroup ] = "sampleForm" ( ngSubmit ) = "onSubmit(sampleForm)" > < div > < label > Name < / label > < input type = "text" name = "name" formControlName = "name" > < / div > < div > < label > Email < / label > < input type = "text" name = "email" formControlName = "email" > < / div > < div > < label > Age < / label > < input type = "text" name = "age" formControlName = "age" > < / div > < div formGroupName = "address" > < h3 > Address < / h3 > < div > < label > Country < / label > < input type = "text" name = "country" formControlName = "country" > < / div > < div > < label > City < / label > < input type = "text" name = "city" formControlName = "city" > < / div > < / div > < pre > { { sampleForm . value | json } } < / pre > < button type = "submit" > Submit < / button > < button type = "button" ( click ) = "printSampleForm()" > Print to console < / button > < / form >

Validation:

In the reactive form we can do validation in two methods.

Using Directive Using form control

Here we are going to reuse the errors.component.ts component to manage custom error message.

// show-errors.component.ts import { Component, Input } from '@angular/core'; import { AbstractControlDirective, AbstractControl } from '@angular/forms'; @Component({ selector: 'show-errors', template: ` <ul *ngIf="shouldShowErrors()" class="validation-errors"> <li style="color: red">{{getError()}}</li> </ul> `, }) export class ShowErrorsComponent { private static readonly errorMessages = { 'required': (params) => '##FIELD## can\'t be blank', 'minlength': (params) => '##FIELD## should be minimum '+params.requiredLength+' characters', 'maxlength': (params) => '##FIELD## should not be greater then '+params.requiredLength+' characters', 'pattern': (params) => 'Should be a valid', 'email': (params) => "Should be vaild email.", }; @Input() private control: AbstractControlDirective | AbstractControl; shouldShowErrors(): boolean { return this.control && this.control.errors && (this.control.dirty || this.control.touched); } listOfErrors(): string[] { return Object.keys(this.control.errors) .map(field => this.getMessage(field, this.control.errors[field],this.control)); } getError(): string { //console.log("show",this.control.errors); var errors = Object.keys(this.control.errors) .map(field => this.getMessage(field, this.control.errors[field],this.control)); return errors[0]; } private getMessage(type: string, params: any,control:any) { var fname = this.getControlName(control); fname = fname.replace("_"," ").replace(" id","").toLowerCase(); fname = fname.replace(/\b\w/g, l => l.toUpperCase()) var msg = ShowErrorsComponent.errorMessages[type](params); return msg.replace("##FIELD##",fname); } getControlName(c: AbstractControl): string | null { const formGroup = c.parent.controls; return Object.keys(formGroup).find(name => c === formGroup[name]) || null; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 // show-errors.component.ts import { Component , Input } from '@angular/core' ; import { AbstractControlDirective , AbstractControl } from '@angular/forms' ; @ Component ( { selector : 'show-errors' , template : ` < ul * ngIf = "shouldShowErrors()" class = "validation-errors" > < li style = "color: red" > { { getError ( ) } } < / li > < / ul > ` , } ) export class ShowErrorsComponent { private static readonly errorMessages = { 'required' : ( params ) = > '##FIELD## can\'t be blank' , 'minlength' : ( params ) = > '##FIELD## should be minimum ' + params . requiredLength + ' characters' , 'maxlength' : ( params ) = > '##FIELD## should not be greater then ' + params . requiredLength + ' characters' , 'pattern' : ( params ) = > 'Should be a valid' , 'email' : ( params ) = > "Should be vaild email." , } ; @ Input ( ) private control : AbstractControlDirective | AbstractControl ; shouldShowErrors ( ) : boolean { return this . control && this . control . errors && ( this . control . dirty || this . control . touched ) ; } listOfErrors ( ) : string [ ] { return Object . keys ( this . control . errors ) . map ( field = > this . getMessage ( field , this . control . errors [ field ] , this . control ) ) ; } getError ( ) : string { //console.log("show",this.control.errors); var errors = Object . keys ( this . control . errors ) . map ( field = > this . getMessage ( field , this . control . errors [ field ] , this . control ) ) ; return errors [ 0 ] ; } private getMessage ( type : string , params : any , control : any ) { var fname = this . getControlName ( control ) ; fname = fname . replace ( "_" , " " ) . replace ( " id" , "" ) . toLowerCase ( ) ; fname = fname . replace ( / \ b \ w / g , l = > l . toUpperCase ( ) ) var msg = ShowErrorsComponent . errorMessages [ type ] ( params ) ; return msg . replace ( "##FIELD##" , fname ) ; } getControlName ( c : AbstractControl ) : string | null { const formGroup = c . parent . controls ; return Object . keys ( formGroup ) . find ( name = > c === formGroup [ name ] ) || null ; } }

Using Directive:

We can reuse the custom validator that we used in our previous article. Directive validation is very easy , we just need to add particular validation directive to HTML form element like we do in template driven forms.

<form [formGroup]="sampleForm" (ngSubmit)="onSubmit(sampleForm)"> <div> <label>Name</label> <input type="text" name="name" formControlName="name" required> <show-errors [control]="sampleForm.controls.name"></show-errors> </div> <div> <label>Email</label> <input type="text" name="email" formControlName="email" required email> <show-errors [control]="sampleForm.controls.email"></show-errors> </div> <div> <label>Age</label> <input type="text" name="age" formControlName="age" age-validate> <show-errors [control]="sampleForm.controls.age"></show-errors> </div> .. <div formGroupName="location"> .. <input type="text" name="country" formControlName="country" required> <show-errors [control]="sampleForm.controls.address.controls.country"></show-errors> .. <input type="text" name="city" formControlName="city"> .. </div> .. </form> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 < form [ formGroup ] = "sampleForm" ( ngSubmit ) = "onSubmit(sampleForm)" > < div > < label > Name < / label > < input type = "text" name = "name" formControlName = "name" required > < show - errors [ control ] = "sampleForm.controls.name" > < / show - errors > < / div > < div > < label > Email < / label > < input type = "text" name = "email" formControlName = "email" required email > < show - errors [ control ] = "sampleForm.controls.email" > < / show - errors > < / div > < div > < label > Age < / label > < input type = "text" name = "age" formControlName = "age" age - validate > < show - errors [ control ] = "sampleForm.controls.age" > < / show - errors > < / div > . . < div formGroupName = "location" > . . < input type = "text" name = "country" formControlName = "country" required > < show - errors [ control ] = "sampleForm.controls.address.controls.country" > < / show - errors > . . < input type = "text" name = "city" formControlName = "city" > . . < / div > . . < / form >

In the above example you can see we added directive required, email and age-validate each will validate and pass error message show-errors component to will displayed from ShowComponent.

Below directive will manage the age-validate custom validator.

import { Directive } from '@angular/core'; // Will import the angular core features. Required for all components , modules, etc... import { NG_VALIDATORS, FormControl, Validator, ValidationErrors } from '@angular/forms'; // Will import the angular forms @Directive({ selector: '[age-validate]', providers: [{provide: NG_VALIDATORS, useExisting: AgeValidatorDirective, multi: true}] }) export class AgeValidatorDirective implements Validator { // Creating class implementing Validator interface validate(c: FormControl): ValidationErrors { const num = Number(c.value); const isValid = !isNaN(num) && num >= 18 && num <= 85; const message = { 'age': { 'message': 'The age must be a valid number between 18 and 85' // Will changes the error defined in errors helper. } }; return isValid ? null : message; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { Directive } from '@angular/core' ; // Will import the angular core features. Required for all components , modules, etc... import { NG_VALIDATORS , FormControl , Validator , ValidationErrors } from '@angular/forms' ; // Will import the angular forms @ Directive ( { selector : '[age-validate]' , providers : [ { provide : NG_VALIDATORS , useExisting : AgeValidatorDirective , multi : true } ] } ) export class AgeValidatorDirective implements Validator { // Creating class implementing Validator interface validate ( c : FormControl ) : ValidationErrors { const num = Number ( c . value ) ; const isValid = ! isNaN ( num ) && num >= 18 && num <= 85 ; const message = { 'age' : { 'message' : 'The age must be a valid number between 18 and 85' // Will changes the error defined in errors helper. } } ; return isValid ? null : message ; } }

The above example is implemented the Reactive Forms validation using directive now we going to learn how to define validation from the component. You can find the example in git here: https://github.com/agiratech/angular4-reactive-form-exercise1

Using form control direct from Component:

Remember we have discussed three arguments which we can pass to the FormGroup, FormControl and FormArray. If yes, the second parameter of the constructor will receive validators as arguments. See below:

this.sampleForm = new FormGroup({ 'name': new FormControl('', Validators.required), 'age': new FormControl('', [Validators.required, CustomValidators.vaildEmail]), 'email': new FormControl('', [Validators.required, CustomValidators.ageValidate]), 'address': new FormGroup({ 'country': new FormControl('', Validators.required), 'city': new FormControl() }) } ); 1 2 3 4 5 6 7 8 9 10 this . sampleForm = new FormGroup ( { 'name' : new FormControl ( '' , Validators . required ) , 'age' : new FormControl ( '' , [ Validators . required , CustomValidators . vaildEmail ] ) , 'email' : new FormControl ( '' , [ Validators . required , CustomValidators . ageValidate ] ) , 'address' : new FormGroup ( { 'country' : new FormControl ( '' , Validators . required ) , 'city' : new FormControl ( ) } ) } ) ;

Above you can see that we have used classes Validators and CustomValidators to give validation. Each FormControl will accept multiple validations. for that, we just need to assign validation array forms To use those class we need to import both classes. Validators class is under ‘@angular/forms’. But for CustomValidator, we should write custom validators like below:

import { FormArray, FormControl, FormGroup, ValidationErrors } from '@angular/forms'; export class CustomValidators { static vaildEmail(c: FormControl): ValidationErrors { const email = c.value; var reg = /^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/ var isValid = true; const message = { 'vaildEmail': { 'message': 'Should be valid email.' } }; if (reg.test(email)) { isValid = true; } else { isValid = false; } return isValid ? null : message; } static ageValidate(c: FormControl): ValidationErrors { const num = Number(c.value); const isValid = !isNaN(num) && num >= 18 && num <= 85; const message = { 'age': { 'message': 'The age must be a valid number between 18 and 85' // Will changes the error defined in errors helper. } }; return isValid ? null : message; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import { FormArray , FormControl , FormGroup , ValidationErrors } from '@angular/forms' ; export class CustomValidators { static vaildEmail ( c : FormControl ) : ValidationErrors { const email = c . value ; var reg = / ^ \ w + ( [ - + . ']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/ var isValid = true; const message = { ' vaildEmail ': { ' message ': ' Should be valid email . ' } }; if (reg.test(email)) { isValid = true; } else { isValid = false; } return isValid ? null : message; } static ageValidate(c: FormControl): ValidationErrors { const num = Number(c.value); const isValid = !isNaN(num) && num >= 18 && num <= 85; const message = { ' age ': { ' message ': ' The age must be a valid number between 18 and 85 ' // Will changes the error defined in errors helper. } } ; return isValid ? null : message ; } }

Above validator class will handle email and age validation return message or null based on user inputs.

Same as template driven we can handle for enabling and disabling submit button using the valid state of the form:

<button [disabled]="!sampleForm.valid">Submit</button> 1 < button [ disabled ] = "!sampleForm.valid" > Submit < / button >

The onSubmit function we have written in the component will return the values with which you can save user entered data to SQL database or NoSQL database.

onSubmit(sampleForm: NgForm) { console.log('Form successful submit.'); console.log(sampleForm.value); } 1 2 3 4 onSubmit ( sampleForm : NgForm ) { console . log ( 'Form successful submit.' ) ; console . log ( sampleForm . value ) ; }

You can access the full form controls by using “sampleForm” and onSubmit. The value will return user entered data as JSON object. You can access specific data using DOT operator like “sampleForm.value.name” with this we can collect data and manage it. You can refer the example in git here: https://github.com/agiratech/angular4-reactive-form-exercise2

In the above example, we have learned about model-driven or Reactive forms and how to work with Reactive forms. It’s easy and simple since we are doing validation using “programmatic” methods. for any queries comment below. Follow Agira Technologies to know more on web development technologies.

If you are looking for angular developers for your project ? Hire Angular developer from Agira Technologies to build best web-apps at affordable rates. Our dedicated Angular developers are well-versed with all the updates and ensure top-notch web applications development to meet your business requirements. For any queries reach us via info@agiratech.com