Angular · Forms

Template-Driven Forms

Template-driven forms let you build a form mostly in HTML: add ngModel to inputs, a few validation attributes, and Angular tracks values and validity for you.

Intermediate 6 min Lesson 19 of 38Examples target Angular 20 with standalone components. Each lesson states the version it was verified against.
Before you start:Two-Way Binding
0/38 in Angular
On this page · 6 min · Intermediate

Learning objectives#

  • Bind fields with ngModel and name
  • Use #f="ngForm" to read form state
  • Validate with required, minlength, email and pattern
  • Submit with (ngSubmit) and reset

Explanation#

Import FormsModule. Every <form> automatically becomes an NgForm; each input with ngModel and a name becomes a control in it.

#f="ngForm" gives you the form in the template: f.valid, f.value, f.reset(). #email="ngModel" gives you one field: email.invalid, email.touched, email.errors.

Standard HTML validation attributes become Angular validators: required, minlength, maxlength, email, pattern, min, max.

Show errors when a field is invalid and touched (the user has left it) — or after a submit attempt (f.submitted).

(ngSubmit)="save(f)" handles submission without a page reload. Template-driven forms suit simple forms; for complex, dynamic or heavily tested forms prefer reactive forms.

Syntax#

HTML Illustrative
<form #f="ngForm" (ngSubmit)="save(f)">
  <input name="email" ngModel required email #email="ngModel" />
  @if (email.invalid && email.touched) { <small>Enter a valid email</small> }
  <button [disabled]="f.invalid">Send</button>
</form>

Code example: A contact form with validation messages#

TypeScriptcontact-form.ts Illustrative
import { Component, signal } from '@angular/core';
import { FormsModule, NgForm } from '@angular/forms';
import { JsonPipe } from '@angular/common';


@Component({
  selector: 'app-contact-form',
  imports: [FormsModule, JsonPipe],
  template: `
    <form #f="ngForm" (ngSubmit)="send(f)" novalidate>
      <label>Name
        <input name="name" ngModel required minlength="2" #name="ngModel" />
      </label>
      @if (name.invalid && (name.touched || f.submitted)) {
        <small class="error">
          @if (name.errors?.['required']) { Name is required. }
          @if (name.errors?.['minlength']) { At least 2 characters. }
        </small>
      }


      <label>Email
        <input name="email" type="email" ngModel required email #email="ngModel" />
      </label>
      @if (email.invalid && (email.touched || f.submitted)) {
        <small class="error">Enter a valid email address.</small>
      }


      <label>Topic
        <select name="topic" [ngModel]="'Order'">
          <option>Order</option><option>Refund</option><option>Other</option>
        </select>
      </label>


      <textarea name="message" ngModel required maxlength="200"></textarea>


      <button type="submit">Send</button>
    </form>


    @if (sent(); as data) { <pre>Sent: {{ data | json }}</pre> }
  `,
  styles: `.error { color: #dc2626; display: block; } label { display: block; margin: 6px 0; }`,
})
export class ContactForm {
  sent = signal<unknown>(null);


  send(form: NgForm) {
    if (form.invalid) return;               // errors are shown because f.submitted is now true
    this.sent.set(form.value);
    form.resetForm({ topic: 'Order' });
  }
}
Expected output
Clicking Send on the empty form shows “Name is required.” and “Enter a valid email address.”. Typing a one-letter name shows “At least 2 characters.” after leaving the field. A valid submission displays the values, e.g. Sent: { "name": "Asha", "email": "asha@example.com", "topic": "Refund", "message": "Where is my order?" }, and resets the form.

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. Each input has a name and ngModel, so it registers as a control in the form.
  2. #name="ngModel" exposes the field’s state; errors?.["required"] tells which rule failed.
  3. Errors appear after the field is touched or after a submit attempt (f.submitted).
  4. [ngModel]="'Order'" sets a default without two-way binding.
  5. send() ignores invalid forms, stores the values and calls resetForm with defaults.

Real-world use case#

A “Contact us” page, a newsletter signup and a simple feedback form are quick to build as template-driven forms, with validation entirely in the markup.

Common mistakes#

Missing name attribute

An input with ngModel but no name inside a form throws an error and does not register.

Showing errors immediately

Errors on untouched fields greet users with red text. Check touched or submitted.

Only disabling the submit button

A disabled button with no messages leaves users guessing. Show which fields are wrong.

Best practices#

  • Use novalidate and Angular’s validation messages
  • Show errors on touched or submitted
  • Keep template-driven forms small
  • Validate again on the server

Remember this#

ngModel + name = a control. #f="ngForm" = the whole form.

required, minlength, email, pattern work as validators.

Show errors when invalid && (touched || submitted).

Practice exercise#

Your task: Build a template-driven signup form with a username (required, pattern of letters and digits only) and show "Letters and numbers only" when the pattern fails.

Need a hint?
pattern="[A-Za-z0-9]+" and check user.errors?.["pattern"].
Show solution
TypeScript Illustrative
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';


@Component({
  selector: 'app-signup',
  imports: [FormsModule],
  template: `
    <form #f="ngForm">
      <input name="username" ngModel required pattern="[A-Za-z0-9]+" #user="ngModel" />
      @if (user.errors?.['pattern'] && user.touched) { <small>Letters and numbers only</small> }
      <button [disabled]="f.invalid">Sign up</button>
    </form>
  `,
})
export class Signup {}

Mini quiz#

Template-Driven Forms quizMultiple choice Question 1 of 3

What must an input with ngModel inside a form also have?

Score so far: 0/0

Want more? Practise all Angular MCQs · Angular interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.