Monday, October 5, 2020

Angular Template-Driven Form Validation Example

In the post Angular Template-Driven Form Example we saw an example of creating a simple template-driven form in Angular but that example lacked one important aspect of forms; validations. In this post we’ll see an example of Angular template-driven form with validations.

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 message 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 not entered for the required field or value is invalid and an error message is also displayed.

Initial form state-

Angular template-driven form validation

With invalid values-

template-driven form validation example

Once the form coding 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 “name” formcontrol, which has the classes ng-untouched ng-pristine ng-invalid added by Angular 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="text" id="name" ngmodel="" name="name" required="" class="form-control ng-untouched ng-pristine ng-invalid" ng-reflect-model="" ng-reflect-name="name" ng-reflect-required="">

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="text" id="name" ngmodel="" name="name" required="" class="form-control ng-dirty ng-valid ng-touched" ng-reflect-model="" ng-reflect-name="name" ng-reflect-required="">

Template-driven form with validation Angular example

1. Importing FormsModule

You need to import FormsModule into the AppModule (app.module.ts) for Angular template-driven form to work. When you import the FormsModule in your component, Angular automatically creates and attaches an NgForm directive to the <form> tag in the template. Using NgForm you can track aggregated form value and validation status.

Add FormsModule in the imports section of the AppModule.

imports: [
    BrowserModule,
    FormsModule
],

2. 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;
  }
}

3. Form Template (app.component.html)

In this Angular template driven form example (as already shown in the image) we’ll have a form with two input fields for name and email, a date picker for picking date and a drop down to select one of the option.

<div class="container">
  <div class="row">
    <div class="col-xs-12 col-sm-10 col-md-8">
      <h1>Membership Form</h1>
      <form (ngSubmit)="onSubmit()" #membershipForm="ngForm">
        <div class="form-group">
          <label for="name">Name</label>
          <input type="text" class="form-control" 
            id="name"
            ngModel name="name" #name="ngModel" required>     
            <div class="alert alert-danger" *ngIf="name.invalid && name.touched">Name is required</div>                  
        </div>
          
        <div class="form-group">
          <label for="email">email</label>
          <input type="email" class="form-control" 
              id="email" required email
              ngModel name="email" #email="ngModel"> 
              <div class="alert alert-danger" *ngIf="email.invalid && 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"
            [ngModel]="currentDate | date:'yyyy-MM-dd'"  name="mdate">                        
        </div>
        <div class="form-group">
          <label for="type">Membership Type</label>
          <select class="form-control" id="type"                    
              ngModel name="type" required #type="ngModel">
            <option *ngFor="let mtype of membershiptypes" [value]="mtype">{{mtype}}</option>
          </select>
          <div class="alert alert-danger" *ngIf="type.invalid && type.touched">
              Please select membership type
          </div> 
        </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. class="form-group" and class="form-control" are BootStrap framework classes used for styling. In Angular form a FormControl represents an individual form control where as FormGroup represents a collection of form controls as a group.
  2. You would have noticed that ngModel is added with every element. This tells Angular that this element is a form control. Only for Membership date ngvalue Model is used with square brackets meaning as a one way binding to get the current date. For other elements it is added only as ngModel to notify that it is a form control.
  3. For name, email and type HTML5 required attribute has been added to specify that a value is required. With email control email attribute has also been added to specify that entered value has to be a valid email. Check the whole list of available Angular validators here- https://angular.io/api/forms/Validators
  4. Note that Angular framework doesn’t directly use HTML5 validator attributes. When it detects an HTML 5 validator attribute Angular adds a directive corresponding to that attribute. For example if Angular detects a control marked with the email attribute, it adds EmailValidator directive.
  5. 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="name.invalid && name.touched">Name is required</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="name.invalid && name.touched".
  6. To add the if condition you need access to the control, that is done by adding a local reference as done for name by adding- #name="ngModel"
  7. 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">

5. Component class (app.component.ts)

import { Component, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Member } from './member.model';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  @ViewChild('membershipForm') memberForm : NgForm;
  membershiptypes = ['Silver', 'Gold', 'Platinum'];
  currentDate = new Date();
  member = new Member('', '', new Date(), '');
  submitted = false;

  onSubmit(){
    this.submitted = true;
    this.member.name = this.memberForm.value.name;
    this.member.mail = this.memberForm.value.email;
    this.member.membershipDate = this.memberForm.value.mdate;
    this.member.membershipType = this.memberForm.value.type; 
  }
}
  1. @ViewChild decorator is used here to access the local reference. Since local reference membershipForm holds a reference to the JS object for the form so this way you can get access to the form object in your type script code.
  2. Here we have currentDate field storing the current date, this is the field bound to the membership date in the template.
  3. You create a member object of the Member class where you will assign the field values when the form is submitted (onSubmit() is called).

That's all for this topic Angular Template-Driven 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 Reactive Form Example
  3. Nested Route (Child Route) in Angular
  4. How to Create a Custom Observable in Angular
  5. Angular Route Resolver - Passing Data Dynamically

You may also like-

  1. Angular Routing Concepts With Example
  2. Angular Access Control CanActivate Route Guard Example
  3. Angular Style Binding With Examples
  4. Angular @Component Decorator
  5. LinkedHashSet in Java With Examples
  6. Java Semaphore With Examples
  7. Python String replace() Method
  8. Dependency Injection in Spring Framework

No comments:

Post a Comment