Friday, March 15, 2024

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-8">
        <h1>Membership Form</h1>
        <form (ngSubmit)="onSubmit()" #membershipForm="ngForm">
          <div class="mb-3">
            <label for="name" class="form-label">Name</label>
            <input type="text" class="form-control" 
              id="name"
              ngModel name="name" #name="ngModel" required minlength="5">     
              <div class="alert alert-danger" *ngIf="name.invalid && (name.dirty || name.touched)">
                <div *ngIf="name.errors?.['required']">
                  Name is required.
                </div>
                <div *ngIf="name.errors?.['minlength']">
                  Name must be at least 5 characters long.
                </div>
              </div>                  
          </div>
          
          <div class="mb-3">
            <label for="email" class="form-label">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="mb-3">
          <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="mb-3">
          <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-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-control" is a BootStrap framework class 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 ngModel is used with square brackets meaning as a one way binding to get the current date through component. 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. With name minlength attribute has been added to ensure that name is atleast of length 5. 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 email control.
    <div class="alert alert-danger" *ngIf="email.invalid && 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 email is not entered which is taken care of by *ngIf="email.invalid && email.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. For name field two error messages are required one for the 'required' validation and another one for minlength="5" validation. That is also possible by individually checking which validation is failing.
  8. Validation classes are added at the form level too apart from 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 {
    //definite assignment assertion
    @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. Angular Reactive Form Validation Example
  4. Angular Form setValue() and patchValue()
  5. FormBuilder in Angular Example

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