Angular Template-Driven Forms with ngModel

Angular Tutorial: Beginner to Advanced · 8. Forms and validation · 3 min read · Updated

Template-driven forms are the second way to build forms in Angular. You describe the form mostly in the template with the ngModel directive. They are quick for small forms, and you will meet them in existing projects.

What you will learn

  • How to use FormsModule and [(ngModel)]
  • How to add validation and read form state in the template
  • When to choose template-driven or reactive forms

A small feedback form

// src/app/feedback-form/feedback-form.ts
import { Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-feedback-form',
  imports: [FormsModule],
  template: `
    <form #feedbackForm="ngForm" (ngSubmit)="submit(feedbackForm.valid)" novalidate>
      <p>
        <label for="email">Email</label><br />
        <input id="email" name="email" type="email" [(ngModel)]="email" required email #emailField="ngModel" />
        @if (emailField.invalid && (emailField.touched || tried())) {
          <span role="alert">Enter a valid email address.</span>
        }
      </p>

      <p>
        <label for="comment">Comment</label><br />
        <textarea id="comment" name="comment" [(ngModel)]="comment" required minlength="10" #commentField="ngModel"></textarea>
        @if (commentField.invalid && (commentField.touched || tried())) {
          <span role="alert">Write at least 10 characters.</span>
        }
      </p>

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

    @if (thanks()) {
      <p role="status">Thank you for your feedback!</p>
    }
  `,
})
export class FeedbackForm {
  protected email = '';
  protected comment = '';
  protected readonly tried = signal(false);
  protected readonly thanks = signal(false);

  protected submit(valid: boolean | null): void {
    this.tried.set(true);
    if (!valid) {
      return;
    }
    this.thanks.set(true);
    this.email = '';
    this.comment = '';
    this.tried.set(false);
  }
}
// src/app/app.ts
import { Component } from '@angular/core';
import { FeedbackForm } from './feedback-form/feedback-form';

@Component({
  imports: [FeedbackForm],
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
})
export class App {}
<!-- src/app/app.html -->
<main>
  <h1>Feedback</h1>
  <app-feedback-form />
</main>

The key parts

  • FormsModule provides the directives, and has to be in imports.
  • [(ngModel)]="email" links an input and a property in both directions, using the two-way binding syntax from the outputs lesson.
  • Each field needs a name attribute, because the form uses it to register the control.
  • required, email and minlength="10" are validation rules written as HTML attributes.
  • #feedbackForm="ngForm" gives the template a variable for the whole form, and #emailField="ngModel" does the same for one field, so you can read valid, touched and errors.

Template-driven or reactive?

  • Choose template-driven for small, simple forms such as a search box or a feedback form, where the logic fits comfortably in the template.
  • Choose reactive for anything larger or dynamic: many fields, custom rules, rules across fields, fields that appear and disappear, or when you want to unit test the form without a template.

Both produce the same kind of controls underneath. Do not mix them in the same form.

Try it yourself

  1. Add a "rating" select field with required validation.
  2. Show a counter such as "7 / 10 characters" under the comment field using comment.length.
  3. Compare this form with the registration form from the previous lesson. Which one would you find easier to test?

Quick check

  1. Which module do you import for ngModel?
  2. Why does each field need a name attribute?
  3. When is a reactive form usually a better choice?

Next, continue with FormControl, FormGroup, FormBuilder, setValue, patchValue, valueChanges and statusChanges.

Frequently asked questions

Is [(ngModel)] the same as property plus event binding?

Yes, it is shorthand for both: it sets the value from your class and updates the class when the user types.

Can I use ngModel inside a reactive form?

Avoid it. Choose one approach per form to prevent confusing double sources of truth.