Angular 17 Template Driven Form Validation example

In this tutorial, I will show you how to implement Angular Template Driven Forms Validation example (and Submit) with Angular 17 and Bootstrap 4.

More Practice:
Angular 17 Form Validation example (Reactive Forms)
Angular 17 File upload example with Rest API
Angular 17 CRUD example with Web API
Angular 17 JWT Authentication & Authorization example

Serverless with Firebase:
Angular 17 Firebase CRUD example
Angular 17 Firestore CRUD example
Angular 17 File Upload with Firebase Storage


Angular 17 Template Driven Forms Validation overview

We will implement validation for a Angular Form using Template Driven Forms and Bootstrap 4. The form has:

  • Full Name: required
  • Username: required, from 6 to 20 characters
  • Email: required, email format
  • Password: required, from 6 to 40 characters
  • Confirm Password: required, same as Password
  • Accept Terms Checkbox: required

angular-17-template-driven-form-validation-example

Some fields could be wrong:

template-driven-form-validation-angular-17-example

Successful Submission will look like this:

angular-17-template-driven-form-validation-example-on-submit

Technology

We’re gonna use following modules:

  • Angular 17
  • Bootstrap 4
  • @angular/forms 17

Setup Project

First we need to add the FormsModule into our App Module.

Open src/app/app.module.ts and import FormsModule from @angular/forms:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from  '@angular/forms';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Import Bootstrap

Open index.html and add following line into <head> tag:

<!DOCTYPE html>
<html lang="en">
  <head>
    ...
    <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css" />
  </head>
  ...
</html>

Another way is installing Bootstrap module with command: npm install [email protected].
Then add following code into angular.json:

"styles": [
    "node_modules/bootstrap/dist/css/bootstrap.min.css",
    "src/styles.css"
],
"scripts": [
    "node_modules/jquery/dist/jquery.slim.min.js",
    "node_modules/popper.js/dist/umd/popper.min.js",
    "node_modules/bootstrap/dist/js/bootstrap.min.js"
]

Template Driven Forms Validation with Angular 17

Template Driven Forms rely on directives defined in the FormsModule. In this example, we will use:

  • NgModel
  • NgForm: creates a top-level FormGroup instance, binds it to a <form> element to track form value and validation status.

We also need to use attributes of following FormsModule directives:

  • RequiredValidator: required
  • MinLengthValidator: minlength
  • MaxLengthValidator: maxlength
  • EmailValidator: email

To get access to the NgForm and the overall form status, we declare a template reference variable #f="ngForm".

<form
  name="form"
  #f="ngForm"
  (ngSubmit)="f.form.valid && onSubmit()"
>

The f template variable is now a reference to the NgForm directive instance. So we can use f.form.valid or f.submitted for example.

Similarly, to inspect the properties of the associated FormControl, we export the directive into a local template variable using ngModel as the key (#username="ngModel").

<input
  type="text"
  class="form-control"
  name="username"
  [(ngModel)]="form.username"
  required
  minlength="6"
  maxlength="20"
  #username="ngModel"
/>

Now we can access the control using the directive’s control property: username.errors.

<div *ngIf="f.submitted && username.errors" class="invalid-feedback">
  <div *ngIf="username.errors['required']">Username is required</div>
  <div *ngIf="username.errors['minlength']">
    Username must be at least 6 characters
  </div>
  <div *ngIf="username.errors['maxlength']">
    Username must be at most 20 characters
  </div>
</div>

Angular 17 component with Template Driven Form

In the component that working with Template Driven Form, let’s create an object (form) that stores all form value. We will bind the form fields with the property of this object.

Then create two functions for Form submission and Form reset: onSubmit() and onReset().

app/app.component.ts

import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  form = {
    fullname: '',
    username: '',
    email: '',
    password: '',
    confirmPassword: '',
    acceptTerms: false,
  };

  onSubmit(): void {
    console.log(JSON.stringify(this.form, null, 2));
  }

  onReset(form: NgForm): void {
    form.reset();
  }
}

Angular Template Driven Form Validation template

Now we create the form with input fields and validation messages.

We bind to the FormGroup object (form) in the app component and use f as template variable. Form submit event will call onSubmit() handler above using event binding (ngSubmit).

Validation messages will display after form submission for the first time by f.submitted value.

app/app.component.html

<div class="register-form">
  <form
    name="form"
    #f="ngForm"
    (ngSubmit)="f.form.valid && onSubmit()"
    novalidate
    [appMatchPassword]="['password', 'confirmPassword']"
  >
    <div class="form-group">
      <label>Full Name</label>
      <input
        type="text"
        class="form-control"
        name="fullname"
        [(ngModel)]="form.fullname"
        required
        #fullname="ngModel"
        [ngClass]="{ 'is-invalid': f.submitted && fullname.errors }"
      />
      @if (f.submitted && fullname.errors) {
      <div class="invalid-feedback">
        @if (fullname.errors['required']) {
        <div>Fullname is required</div>
        }
      </div>
      }
    </div>

    <div class="form-group">
      <label>Username</label>
      <input
        type="text"
        class="form-control"
        name="username"
        [(ngModel)]="form.username"
        required
        minlength="6"
        maxlength="20"
        #username="ngModel"
        [ngClass]="{ 'is-invalid': f.submitted && username.errors }"
      />
      @if (f.submitted && username.errors ){
      <div class="invalid-feedback">
        @if (username.errors['required']) {
        <div>Username is required</div>
        }
		@if (username.errors['minlength']) {
        <div>Username must be at least 6 characters</div>
        }
		@if (username.errors['maxlength']) {
        <div>Username must be at most 20 characters</div>
        }
      </div>
      }
    </div>

    <div class="form-group">
      <label>Email</label>
      <input
        type="email"
        class="form-control"
        name="email"
        [(ngModel)]="form.email"
        required
        email
        #email="ngModel"
        [ngClass]="{ 'is-invalid': f.submitted && email.errors }"
      />
      @if (f.submitted && email.errors) {
      <div class="invalid-feedback">
        @if (email.errors['required']) {
        <div>Email is required</div>
        }
		@if (email.errors['email']) {
        <div>Email is invalid</div>
        }
      </div>
      }
    </div>

    <div class="form-group">
      <label>Password</label>
      <input
        type="password"
        class="form-control"
        name="password"
        [(ngModel)]="form.password"
        required
        minlength="6"
        maxlength="40"
        #password="ngModel"
        [ngClass]="{ 'is-invalid': f.submitted && password.errors }"
      />
      @if (f.submitted && password.errors) {
      <div class="invalid-feedback">
        @if (password.errors['required']) {
        <div>Password is required</div>
        }
		@if (password.errors['minlength']) {
        <div>Password must be at least 6 characters</div>
        }
		@if (password.errors['maxlength']) {
        <div>Username must not exceed 40 characters</div>
        }
      </div>
      }
    </div>

    <div class="form-group">
      <label>Confirm Password</label>
      <input
        type="password"
        class="form-control"
        name="confirmPassword"
        [(ngModel)]="form.confirmPassword"
        required
        #confirmPassword="ngModel"
        [ngClass]="{ 'is-invalid': f.submitted && confirmPassword.errors }"
      />
      @if (f.submitted && confirmPassword.errors) {
      <div class="invalid-feedback">
        @if (confirmPassword.errors['required']) {
        <div>Confirm Password is required</div>
        }
		@if (confirmPassword.errors['matching']) {
        <div>Confirm Password does not match</div>
        }
      </div>
      }
    </div>

    <div class="form-group form-check">
      <input
        type="checkbox"
        lass="form-control"
        name="acceptTerms"
        [(ngModel)]="form.acceptTerms"
        class="form-check-input"
        required
        #acceptTerms="ngModel"
        [ngClass]="{ 'is-invalid': f.submitted && acceptTerms.errors }"
      />
      <label for="acceptTerms" class="form-check-label">
        I have read and agree to the Terms
      </label>
      @if (f.submitted && acceptTerms.errors) {
      <div class="invalid-feedback">Accept Terms is required</div>
      }
    </div>

    <div class="form-group">
      <button type="submit" class="btn btn-primary">Register</button>
      <button
        type="button"
        (click)="onReset(f)"
        class="btn btn-warning float-right"
      >
        Reset
      </button>
    </div>
  </form>
</div>

You can see that we has appMatchPassword directive in the form: [appMatchPassword]="['password', 'confirmPassword']". Let’s continue to the next section for more details.

Confirm Password validation in Template Driven Form

Now we will create Validation class to implement password and confirm password validation.

– First, the validator returns null (meaning validation has passed) if there is any error on the control that we want to check (confirm password).
– Next, the validator checks that two fields match or not and set error (matching property value to true) on checking control if validation fails.

utils/validation.ts

import { FormGroup } from '@angular/forms';

export default class Validation {
  static match(controlName: string, checkControlName: string) {
    return (formGroup: FormGroup) => {
      const control = formGroup.controls[controlName];
      const checkControl = formGroup.controls[checkControlName];

      if (checkControl?.errors && !checkControl.errors['matching']) {
        return null;
      }

      if (control?.value !== checkControl?.value) {
        checkControl?.setErrors({ matching: true });
        return { matching: true };
      } else {
        checkControl?.setErrors(null);
        return null;
      }
    };
  }
}

Then we create a custom Directive to implement custom validator for the Template Driven Form.

Run the command: ng g d directives/passwordPattern
You will see a folder named directives is created with two files inside it: match-password.directive.ts and match-password.directive.spec.ts.

We will implement the Validator interface on MatchPasswordDirective class and override the validate() method. This directive is used to validate if the password and confirmPassword are matching or not.

Open directives/match-password.directive.ts and write following code:

import { Directive, Input } from '@angular/core';
import { FormGroup, NG_VALIDATORS, ValidationErrors, Validator } from '@angular/forms';

import Validation from '../utils/validation';

@Directive({
  selector: '[appMatchPassword]',
  providers: [{ provide: NG_VALIDATORS, useExisting: MatchPasswordDirective, multi: true }]
})
export class MatchPasswordDirective implements Validator {
  @Input('appMatchPassword') matchPassword: string[] = [];

  validate(formGroup: FormGroup): ValidationErrors | null {
    return Validation.match(this.matchPassword[0], this.matchPassword[1])(formGroup);
  }
}

The input is an array of Strings, which contains 2 fields to match. In the validate() method, we call Validation.match() static method.

Run Template Driven Form Validation with Angular 17 example

You can run our App with command: ng serve.
If the process is successful, open Browser with Url: http://localhost:4200/ and check it.

Or run on Stackblitz:

Conclusion

Today we’ve built Angular 17 Template Driven Forms Validation example successfully with Angular Forms module & Bootstrap 4.

You can also use the Form Validation in following posts:
Angular 17 File upload example with Rest API
Angular 17 CRUD example with Web API
Angular 17 JWT Authentication example with Web Api

Or using Angular Reactive Forms Module:
Angular 17 Form Validation example (Reactive Forms)

Happy learning! See you again.

Further Reading

Serverless with Firebase:
Angular 17 Firebase CRUD example
Angular 17 Firestore CRUD example
Angular 17 File Upload with Firebase Storage

Fullstack CRUD Application:
Angular + Node Express + MySQL example
Angular + Node Express + PostgreSQL example
Angular + Node Express + MongoDB example
Angular 17 + Spring Boot + H2 example
Angular 17 + Spring Boot + MySQL example
Angular 17 + Spring Boot + PostgreSQL example
Angular 17 + Spring Boot + MongoDB example
Angular + Django example
Angular + Django + MySQL example
Angular + Django + PostgreSQL example
Angular + Django + MongoDB example