Quick Introduction to Template-Driven Forms in Angular

We’ve covered reactive forms in Angular, so now let’s cover an alternative way to declare and work with forms: template-driven forms. Template-driven forms are similar to how forms work in AngularJS and they can be simpler to work with because everything is defined in the template. They lack in flexibility however, so you’ll want to use them mostly for simpler forms. Behind the scenes, template-driven forms are converted to the model-driven equivalent by Angular, to the internal functioning is the same.

This post applies to Angular 2+


First, you’ll want to make sure that the FormsModule is imported in your app or feature module:


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

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

  declarations: [
  imports: [
  providers: [],
  bootstrap: [AppComponent]
export class AppModule { }

And that’s it really, you’re ready to start building forms that are powered by Angular!

First Form

Let’s create a simple user registration form. We’ll add everything in at once with interesting parts highlighted, and then we’ll break down each of the directives:


<form #newUserForm="ngForm" (ngSubmit)="onSubmit(newUserForm)">

  <input type="text" placeholder="User name"
         required maxlength="25"
         [(ngModel)]="userName" name="userName"
  <div *ngIf="!pickedName.valid && pickedName.touched">
    User name is required!

  <input type="email" placeholder="Email"
         required [(ngModel)]="email"
         name="email" #userEmail="ngModel">
  <div *ngIf="!userEmail.valid && userEmail.touched">
    Email is required!

  <input type="text" placeholder="Nick name"
         [(ngModel)]="nickName" name="nickName">

  <input type="password" placeholder="Password" required
         [(ngModel)]="password" name="password"
  <div *ngIf="!userPassword.valid && userPassword.touched">
    Password is required!

  <button type="submit" [disabled]="!newUserForm.form.valid">

  <button type="button" (click)="newUserForm.reset()">


With this in place, you could add something like the following to your template temporarily to see the state of your form while working on it:

<pre>{{ newUserForm.form.value | json }}</pre> <!-- Value of whole form -->
<pre>User name: {{ pickedName.value }}</pre> <!-- Value of userName field -->
<pre>Valid form? {{ newUserForm.form.valid | json }}</pre> <!-- Validity of whole form -->

Our component class looks like this:


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

@Component({ ... })
export class AppComponent {
  userName = 'Bob';
  email: string;
  nickName: string;
  password: string;

  onSubmit(form: NgForm) {
    if (form.valid) {
      // ...our form is valid, we can submit the data

Here the field that's bound to userName will have the initial value of Bob.

Here’s a quick breakdown of what we have with our form:

  • #newUserForm=”ngForm”: Angular automatically attaches an ngForm directive to HTML form elements, but with this, we give our form a template reference name to make it easy to access its value and validity.
  • With template-driven forms, each field’s validity is controlled by the standard HTML5 validity attributes like required, maxlength or pattern. Each form field will be either valid or invalid and the form as a whole is valid only when each field is valid.
  • We can create template references for form fields with the #someName=”ngModel” syntax. We can then use these references to access the value or validity of these fields.
  • With a template reference in place for a particular field, we can setup messages in the template that are shown (ngIf) depending on states of the field (valid, pristine, touched). Pristine if for when the value of the field hasn’t been changed, and touched is for when a field was focused and then unfocused.
  • Form fields are bound to properties in your component class using the banana in a box syntax: [(ngModel)]=”userName”. This effectively creates two-way data binding, so if you change the value of a property in the component class the change will be reflected in the template.
  • Each form field that has an ngModel directive should also have a name attribute set and that’s the name that Angular will use internally for the form’s model representation. The value for name doesn’t have to be the same as the value bound in the component class using [(ngModel)].
  • Submitting: The form hooks into the ngSubmit event that Angular provides for template-driven forms and calls an onSubmit method that we define with our form passed-in. A user clicking on the button of type submit will trigger that ngSubmit event.
  • We bind to the submit button’s disabled property to disable the button when the form as a whole is invalid.
  • Forms can be reset by calling the reset() method on them.

Styling & validity states

The form element as a whole as well as each form field will automatically have classes applied to them:

  • ng-invalid / ng-valid: ng-invalid if the field doesn’t satisfy the specified validity requirements, ng-valid otherwise.
  • ng-dirty / ng-pristine: ng-dirty if the field value was changed by the user, and ng-pristine otherwise.
  • ng-touched / ng-untouched: ng-touched if the field has been focused and then blurred (user accessed it), and ng-untouched otherwise.

Tip: Open your browser's element inspector to see the classes being applied as you interact with the form fields.

This allows to define simple CSS rules to style the different fields of a form depending on the validity state:


.ng-invalid.ng-touched:not(form) {
  border: 2px solid red;

With that, invalid fields that have been touched will get a red border. It’s good practice to wait for a field to have been touched to show an invalid style, otherwise it can feel weird UX-wise. Notice here that we exclude the form element (:not(form)), as otherwise the whole form would also get a red border when a field has been touched and is invalid.

✖ Clear

🕵 Search Results

🔎 Searching...