Tuesday, October 6, 2020

Angular Reactive Form Validation Example

In the post Angular Reactive Form Example we saw an example of creating a simple Reactive form in Angular in this post we’ll add to that example to create an Angular reactive form with validation.

Tracking control states

NgModel directive used with the form controls tracks the state of that control. Three things you'll look for while validating form fields are-

  1. Whether user touched the control or not,
  2. If the value of the form control is changed
  3. If the entered value is valid or invalid.

Angular sets special CSS classes on the control element to reflect the state, as shown in the following table.

State Class if true Class if false
The control has been visited.ng-touchedng-untouched
The control's value has changed.ng-dirtyng-pristine
The control's value is valid.ng-validng-invalid

Using these CSS Classes we can create CSS to highlight the required fields and add error messages if the entered values are invalid.

What we’ll create is a form with green bars at the left side of the controls that are required. That bar changes to red if value is not entered for the required field or value is invalid, an error message is also displayed.

Initial form state-

Angular Reactive Form Validation Example

With invalid values-

Once the form is done and you run it and then inspect it, you can notice that the CSS classes to reflect the state are added. As an example here we have the excerpt for the “email” formcontrol, which has the classes ng-untouched ng-pristine ng-invalid to suggest that the control is still not touched and it is in invalid state as a value is required in this control.

<input type="email" id="email" formcontrolname="email" class="form-control ng-untouched ng-pristine ng-invalid" ng-reflect-name="email">

If we enter some value and then inspect the same element you can see that the classes are also changed. Now the added CSS classes are ng-dirty ng-valid ng-touched suggesting that the control has been touched, value is changed and the value is valid.

<input type="email" id="email" formcontrolname="email" class="form-control ng-dirty ng-valid ng-touched" ng-reflect-name="email">

Reactive form with validation Angular example

Importing ReactiveFormsModule

You need to import ReactiveFormsModule into the AppModule (app.module.ts) for Angular template-driven form to work. Add ReactiveFormsModule in the imports section of the AppModule.

 imports: [
    BrowserModule,
    ReactiveFormsModule
 ],

Data Model (member.model.ts)

Member class defines the data model reflected in the form.

export class Member {
  name: string;
  mail: string;
  membershipDate: Date;
  membershipType: string;
  constructor(name: string, mail: string, membershipDate: Date, membershipType: string) {
    this.name = name;
    this.mail = mail;
    this.membershipDate  = membershipDate;
    this.membershipType = membershipType;
  }
}

Component class (app.component.ts)

In ReactiveForm you create the form controls in your code so Component class is the appropriate place to create such controls which you will sync up with the corresponding template.

In Angular form a FormControl represents an individual form control where as FormGroup represents a collection of form controls as a group. So, it is obvious that in your code you will create a FormGroup object and with in that you will add form controls corresponding to fields in the form.

import { DatePipe } from '@angular/common';
import { Component, OnInit} from '@angular/core';
import { FormControl, FormGroup, Validators} from '@angular/forms';
import { Member } from './member.model';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html', 
  providers: [DatePipe]
})
export class AppComponent implements OnInit {
  membershiptypes = ['Silver', 'Gold', 'Platinum'];
  currentDate = new Date();
  member = new Member('', '', new Date(), '');
  submitted = false;
  membershipForm: FormGroup;
  constructor(private datePipe: DatePipe){ }
  ngOnInit() {
    this.membershipForm = new FormGroup({
      memberName: new FormControl(null, [Validators.required, Validators.minLength(5)]),
      email: new FormControl(null, [Validators.required, Validators.email]),
      //Validators.pattern("[^ @]*@[^ @]*")      
      mdate: new FormControl(this.datePipe.transform(this.currentDate, 'yyyy-MM-dd')),
      membershipType: new FormControl('Silver')
    });
  }
  onSubmit(){
    this.submitted = true;
    this.member.name = this.membershipForm.value.memberName;
    this.member.mail = this.membershipForm.value.email;
    this.member.membershipDate = this.membershipForm.value.mdate;
    this.member.membershipType = this.membershipForm.value.membershipType; 
  }
}

Important points to note here are-

  1. You need to import FormControl, FormGroup and Validators classes.
  2. A reference to FormGroup is created named membershipForm.
  3. Using that reference a new FormGroup object is created and an array of form controls are added to that FormGroup object.
  4. Reactive forms include a set of built-in validator functions for common use cases. These functions receive a control to validate against and return an error object or a null value based on the validation check.
  5. A FromControl takes three arguments state, Validator and AsyncValidator.
  6. While creating a form control you can pass the required Validators, either single or multiple. If multiple validators are passed you pass them as an array of validators as shown here.
    memberName: new FormControl(null, [Validators.required, Validators.minLength(5)])
    
  7. There is also a Validators.pattern function where you can pass a regular expression. As an example in the component for the email control pattern is also used.

app.component.html

<div class="container">
  <div class="row">
    <div class="col-xs-12 col-sm-10 col-md-8">
      <h1>Membership Form</h1>
      <form [formGroup]="membershipForm" (ngSubmit)="onSubmit()">
        <div class="form-group">
          <label for="memberName">Name</label>
          <input type="text" class="form-control" id="memberName"
            formControlName="memberName">       
          <div class="alert alert-danger" *ngIf="membershipForm.get('memberName').invalid 
            && membershipForm.get('memberName').touched">
            <div *ngIf="membershipForm.get('memberName').errors.required">
              Name is required.
            </div>
            <div *ngIf="membershipForm.get('memberName').errors.minlength">
              Name must be at least 5 characters long.
            </div>
          </div>                      
        </div>
        <div class="form-group">
          <label for="email">email</label>
          <input type="email" class="form-control" id="email"
          formControlName="email">  
          <div class="alert alert-danger" *ngIf="membershipForm.get('email').invalid 
              && membershipForm.get('email').touched">
            Please enter a valid email
          </div>                      
        </div>
        <div class="form-group">
          <label for="mdate">Membership Date</label>
          <input type="date" class="form-control" id="mdate"
          formControlName="mdate">                        
        </div>
        <div class="form-group">
          <label for="type">Membership Type</label>
          <select class="form-control" id="type"                    
          formControlName="membershipType">
            <option *ngFor="let mtype of membershiptypes" [value]="mtype">{{mtype}}</option>
          </select>
        </div>
        <button type="submit" [disabled]="membershipForm.invalid" class="btn btn-success">Submit</button>
      </form> 
    </div>
  </div>
  <hr>
  <div *ngIf="submitted">
    <div class="row">
      <div class="col-xs-12 col-sm-10 col-md-8">
        <p>Name: {{member.name}}</p>
        <p>email: {{member.mail}}</p>
        <p>Membership Date: {{member.membershipDate | date:'dd/MM/yyyy'}}</p>
        <p>Membership Type: {{member.membershipType}}</p>
      </div>
    </div>
  </div>  
</div>

Important points to note here are-

  1. You need to tell the template from where it has to get all the FormControls for the form that is done by binding the FormGroup reference.
    <form [formGroup]="membershipForm" (ngSubmit)="onSubmit()">
    
  2. For syncing up the form controls you will have to bind each form field with the form control created in the component. That is done by assigning formControlName to the name given in the component.
    <input type="text" class="form-control" id="name"formControlName="memberName"> 
    
  3. Error message that is shown beneath the form controls is done by adding a <div> with the form control where message is needed. For example if you want to show error message for name control.
    <div class="alert alert-danger" *ngIf="membershipForm.get('email').invalid 
                  && membershipForm.get('email').touched">
                Please enter a valid email
    </div>       
    
    Here alert and alert-danger are Bootstrap classes. Message is displayed if the control has been visited and name is not entered which is taken care of by
    *ngIf="membershipForm.get('email').invalid && membershipForm.get('email').touched". 
    
  4. For accessing form control you can use get() method and pass the control name.
  5. Validation classes are added at the form level too apart from with each form control to determine whether the form as a whole is valid or not. Using that you can disable the submit button and enable it only when the whole form is valid. That’s what [disabled]="membershipForm.invalid" does in the submit button.

4. CSS class (src/assets/forms.css)

You will also need to add a CSS class to show a bar on the left side of the control.

.ng-invalid.ng-untouched:not(form)  {
  border-left: 5px solid #42A948; /* green */
}

.ng-invalid.ng-touched:not(form)  {
  border-left: 5px solid #a94442; /* red */
}

.ng-invalid.ng-untouched:not(form) means select all elements having both ng-invalid and ng-untouched set within its class attribute and it is not at form level. If you won’t add not(form) you will have a bar at the form level too encompassing all controls.

Same way .ng-invalid.ng-touched:not(form) means select all elements having both ng-invalid and ng-touched set within its class attribute and it is not at form level.

In the index.html file, update the <head> tag to include the new style sheet.

<link rel="stylesheet" href="assets/forms.css">

That's all for this topic Angular Reactive Form Validation Example. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Angular Tutorial Page


Related Topics

  1. Forms in Angular
  2. Angular Template-Driven Form Example
  3. Angular Template-Driven Form Validation Example
  4. Angular Cross Component Communication Using Subject Observable
  5. Nested Route (Child Route) in Angular

You may also like-

  1. Location Strategies in Angular Routing
  2. Navigate to a Route Programmatically in Angular
  3. Angular Custom Two-Way Data Binding
  4. Angular ngIf Directive With Examples
  5. Java ArrayBlockingQueue With Examples
  6. static Keyword in Java With Examples
  7. final Vs finally Vs finalize in Java
  8. Spring MVC Excel Generation Example

No comments:

Post a Comment