Published: April 07 2023

Angular Template-Driven Forms - Validation on Submit

Built with Angular 14.2 and Template-Driven Forms

This is a quick example of how to trigger form validation on submit with Template-Driven Forms in Angular.

By default form validation messages are displayed on input fields as soon as they are edited (a.k.a. touched or dirty).

The example code is a simple registration form from an Angular validation tutorial I posted recently, for more info and a live demo see Angular 14 - Template-Driven Form Validation Example.

Trigger Validation on Form Submit

I setup the form to validate on submit rather than as soon as each field is changed, this is implemented using the f.submitted property of the form template variable (#f="ngForm"), the property equals true after the form is submitted for the first time.

In the Angular component template I wrapped the validation messages with an *ngIf directive that only displays when the form is submitted and there are errors (e.g. *ngIf="f.submitted && email.errors").

Angular Component Template with Validate on Submit

The app component template contains all the html markup for displaying the example registration form in the browser. The form submit event is bound to the onSubmit() method of the app component with the event binding (ngSubmit)="onSubmit(f)".

The template variable #f="ngForm" creates a FormGroup instance to provide access to the form submitted property for displaying validation messages on submit.

<!-- main app container -->
<div class="card m-3">
    <h5 class="card-header text-center">Angular 14 Template-Driven Form Validation</h5>
    <div class="card-body">
        <form #f="ngForm" (ngSubmit)="onSubmit(f)" [mustMatch]="['password', 'confirmPassword']" novalidate>
            <div class="row">
                <div class="col mb-3">
                    <label class="form-label">Title</label>
                    <select name="title" ngModel #title="ngModel" class="form-control" [ngClass]="{ 'is-invalid': f.submitted && title.invalid }" required>
                        <option value=""></option>
                        <option value="Mr">Mr</option>
                        <option value="Mrs">Mrs</option>
                        <option value="Miss">Miss</option>
                        <option value="Ms">Ms</option>
                    <div *ngIf="f.submitted && title.errors" class="invalid-feedback">
                        <div *ngIf="title.errors.required">Title is required</div>
                <div class="col-5 mb-3">
                    <label class="form-label">First Name</label>
                    <input type="text" name="firstName" ngModel #firstName="ngModel" class="form-control" [ngClass]="{ 'is-invalid': f.submitted && firstName.invalid }" required>
                    <div *ngIf="f.submitted && firstName.errors" class="invalid-feedback">
                        <div *ngIf="firstName.errors.required">First Name is required</div>
                <div class="col-5 mb-3">
                    <label class="form-label">Last Name</label>
                    <input type="text" name="lastName" ngModel #lastName="ngModel" class="form-control" [ngClass]="{ 'is-invalid': f.submitted && lastName.invalid }" required>
                    <div *ngIf="f.submitted && lastName.errors" class="invalid-feedback">
                        <div *ngIf="lastName.errors.required">Last Name is required</div>
            <div class="row">
                <div class="col mb-3">
                    <label class="form-label">Date of Birth</label>
                    <input type="date" name="dob" ngModel #dob="ngModel" class="form-control" [ngClass]="{ 'is-invalid': f.submitted && dob.invalid }" required pattern="^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$">
                    <div *ngIf="f.submitted && dob.errors" class="invalid-feedback">
                        <div *ngIf="dob.errors.required">Date of Birth is required</div>
                        <div *ngIf="dob.errors.pattern">Date of Birth must be a valid date in the format YYYY-MM-DD</div>
                <div class="col mb-3">
                    <label class="form-label">Email</label>
                    <input type="text" name="email" ngModel #email="ngModel" class="form-control" [ngClass]="{ 'is-invalid': f.submitted && email.invalid }" required email>
                    <div *ngIf="f.submitted && email.errors" class="invalid-feedback">
                        <div *ngIf="email.errors.required">Email is required</div>
                        <div *ngIf="">Email must be a valid email address</div>
            <div class="row">
                <div class="col mb-3">
                    <label class="form-label">Password</label>
                    <input type="password" name="password" ngModel #password="ngModel" class="form-control" [ngClass]="{ 'is-invalid': f.submitted && password.invalid }" required minlength="6">
                    <div *ngIf="f.submitted && password.errors" class="invalid-feedback">
                        <div *ngIf="password.errors.required">Password is required</div>
                        <div *ngIf="password.errors.minlength">Password must be at least 6 characters</div>
                <div class="col mb-3">
                    <label class="form-label">Confirm Password</label>
                    <input type="password" name="confirmPassword" ngModel #confirmPassword="ngModel" class="form-control" [ngClass]="{ 'is-invalid': f.submitted && confirmPassword.invalid }" required>
                    <div *ngIf="f.submitted && confirmPassword.errors" class="invalid-feedback">
                        <div *ngIf="confirmPassword.errors.required">Confirm Password is required</div>
                        <div *ngIf="confirmPassword.errors.mustMatch">Passwords must match</div>
            <div class="form-check mb-3">
                <input type="checkbox" name="acceptTerms" id="acceptTerms" ngModel #acceptTerms="ngModel" class="form-check-input" [ngClass]="{ 'is-invalid': f.submitted && acceptTerms.invalid }" required>
                <label for="acceptTerms" class="form-check-label">Accept Terms & Conditions</label>
                <div *ngIf="f.submitted && acceptTerms.errors" class="invalid-feedback">Accept Ts & Cs is required</div>
            <div class="text-center">
                <button class="btn btn-primary me-1">Register</button>
                <button class="btn btn-secondary" type="reset">Cancel</button>

Angular Component that handles Form Data

The component doesn't do much when using angular template-driven forms since the form fields and validators are defined in the component template.

The onSubmit() method is called with the NgForm template variable when the form is submitted, it is bound to the form element in the template using Angular event binding syntax ((ngSubmit)="onSubmit(f)"). If valid a simple javascript alert is displayed with the values entered into the form.

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

@Component({ selector: 'app-root', templateUrl: 'app.component.html' })
export class AppComponent {
    onSubmit(f: NgForm) {
        // stop here if form is invalid
        if (f.invalid) {

        alert('SUCCESS!! :-)\n\n' + JSON.stringify(f.value, null, 4));


Need Some Angular Help?

Search fiverr for freelance Angular developers.

Follow me for updates

On Twitter or RSS.

When I'm not coding...

Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!


Supported by