Template Driven Forms are the simplest way to create a form in Angular Application. We need not have to create FormGroup and FormControl inside the Component.

Angular provides built-in directives to bind the HTML form with the model object of Component. using these directives Angular creates FormGroup and FormControl internally internally us.

In this previous chapter, We have created Add Product form using model-driven forms approach.

In this chapter, We will create same Add Product form using template driven forms approach. So that you can understand the difference between both types of forms. 

Create a new component called AddProductTemplateComponent using below command on terminal.

Here, I have used the ‘Template’ in component name, to differentiate it as a Template Driven Form. 

ng g c add-product-template
Create Template Driven Forms Component

Now to display this component of screen add selector of AddProductTemplateComponent in app.component.html. as shown below

<div class="jumbotron jumbotron-fluid">
  <div class="container">
    <h1 class="display-3">Welcome to Electronic-Shop</h1>
    <p class="lead">THE ONLINE MEGASTORE</p>
    <hr class="my-2">
  </div>
</div>
<div class="container-fluid">
  <app-add-product-template>Loading</app-add-product-template>
</div>

Create below HTML template in add-product-template.component.html. This template is same as we have created initially in model driven forms.

<div class="card">
  <div class="card-body">
    <h4 class="card-title">ADD PRODUCT (Template Driven Form)</h4>
    <div class="row">

      <div class="col-md-6">
        <form novalidate>
          <div class="form-group">
            <label>Title</label>
            <input type="text" class="form-control">
          </div>
          <div class="form-group ">
            <label>Model Name</label>
            <input type="text" class="form-control">
          </div>
          <div class="form-group ">
            <label>Color</label>
            <input type="text" class="form-control">
          </div>
          <div class="form-group ">
            <label>Product Type</label>
            <select class="form-control">
              <option *ngFor="let p of productTypes" [value]="p">{{p}}</option>
            </select>
          </div>
          <div class="form-group ">
            <label>Brand</label>
            <input type="text" class="form-control">
          </div>
          <div class="form-group">
            <label>Price</label>
            <input type="text" class="form-control">
          </div>
          <button type="submit" class="btn btn-primary">Submit</button>
          <button class="btn btn-default">Reset</button>
        </form>
      </div>

    </div>
  </div>
</div>

We want the select box to select the list of product types for the user to select. We can hardcode this list, but later on, whenever we need to update Product Type list we need to add extra tags.

Instead of this, we can make this dynamic by creating an array of productTypes inside the component. Using it in the template as shown below.

<select class="form-control">
     <option *ngFor="let p of productTypes" [value]="p">{{p}}</option>
</select>

This will look as below form.

Template Driven Forms initial HTML

For template driven forms we need to import FormsModule in the @NgModule .

Import FormsModule from @angular/forms library in app.module.ts and add it @NgModule.imports array. as shown below,

import { FormsModule } from '@angular/forms';
…

@NgModule({
 …
 imports: [
  …    
        FormsModule
 ]
  …
})
export class AppModule { }

ngForm and ngModel are Angular Directives which are essential to creating Angular Template Driven Forms.

Lets start with ngForm first. 

ngForm Creates a top-level FormGroup instance and binds it to a form to track aggregate form value and validation status.

Angular Docs

NgForm directive has a selector which matches the HTML form tag.

We can export the directive into a local template variable using ngForm as below.

<form #myForm="ngForm">...</form>

Here #myForm is the local template reference variable which refers to the ngForm directive.

One of the things the ngForm directive does is create a top level FormGroup

So that we can use #myForm variable to access the properties of Angular Form like value, valid, invalid and errors.

So just like model driven forms we can output that to screen simply with a pre tag and the json pipe, as shown below

<pre > {{myForm.value | json}} </pre>

If we run this code this will give use blank { } object. This is because ngForm directive doesn’t automatically detect all the controls that exist inside the <form> tag it’s linked to. 

We need to explicitly register each template control with the ngForm directive. To do so we need to do two things to each template form control:

  1. Add the ngModel directive
  2. Add the name attribute

The NgModel directive creates the FormControl instance to manage the template form control and the name attribute tells the NgModel directive what key to store for that FormControl in the parent FormGroup, as shown below :

<div class="form-group">
            <label>Title</label>
            <input type="text" class="form-control" name="title" ngModel>
</div>

Now if you run an application you will get myForm.value as below :

{
  'title': ""
}

And once you change the title field, json will change, so title template control is linked.

The same way we need to link all other controls as shown below. 

<div class="card">
  <div class="card-body">
    <h4 class="card-title">ADD PRODUCT (Template Driven Form)</h4>
    <div class="row">
      <div class="col-md-6">
        <form novalidate>
          <div class="form-group">
            <label>Title</label>
            <input type="text" class="form-control" name="title" ngModel>
          </div>
          <div class="form-group ">
            <label>Model Name</label>
            <input type="text" class="form-control" name="modelName" ngModel>
          </div>
          <div class="form-group ">
            <label>Color</label>
            <input type="text" class="form-control" name="color" ngModel>
          </div>
          <div class="form-group ">
            <label>Product Type</label>
            <select class="form-control" name="productType" ngModel>
              <option *ngFor="let p of productTypes" [value]="p">{{p}}</option>
            </select>
          </div>
          <div class="form-group ">
            <label>Brand</label>
            <input type="text" class="form-control" name="brand" ngModel>
          </div>
          <div class="form-group">
            <label>Price</label>
            <input type="text" class="form-control" name="price" ngModel>
          </div>
          <button type="submit" class="btn btn-primary">Submit</button>
          <button class="btn btn-default">Reset</button>
        </form>
      </div>
    </div>
  </div>
</div>

Once we link all controls, we will get below myForm.value, and as you change the input fields, myForm.value will also change.

{
  "title": "",
  "modelName": "",
  "color": "",
  "productType": "",
  "brand": "",
  "price": ""
}

Another way to work with template driven forms is using Two-way data binding.

We can use ngModel directive to setup two-way data binding between a template form control and a variable on our component.

So when the user changes the value in the template form control the value of the variable on the component automatically updates and when we change the variable on the component the template form control automatically updates.

To map template control using two-way data binding, first we need to create a variable inside component with which we will map the control and then use below syntax in template control tag.

[(ngModel)]="variable-name"

as shown below, I have created one title variable in TestComponent and then I have linked it with title template control using [(ngModel)].

export class TestComponent {
  title = 'Angular App';
}
<label>Title</label>
<input type="text" name="title" [(ngModel)]="title">
<h1>Title : {{title}}</h1>

The same way, we will create one product object of type Product Domain model inside the AddProductTemplateComponent. and link it with the HTML form Template control using [(ngModel)], as shown below.

import { Product } from './../models/product';
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-add-product-template',
  templateUrl: './add-product-template.component.html',
  styleUrls: ['./add-product-template.component.css']
})
export class AddProductTemplateComponent implements OnInit {

  product: Product = new Product();
  productTypes = ['Laptop', 'Mobile'];
  constructor() { }

  ngOnInit() {
  }

}
<div class="card">
  <div class="card-body">
    <h4 class="card-title">ADD PRODUCT (Template Driven Form)</h4>
    <div class="row">
      <div class="col-md-6">
        <form #myForm="ngForm" novalidate>
          <div class="form-group">
            <label>Title</label>
            <input type="text" class="form-control" name="title" [(ngModel)]="product.title" >
          </div>
          <div class="form-group ">
            <label>Model Name</label>
            <input type="text" class="form-control" name="modelName" [(ngModel)]="product.modelName">
          </div>
          <div class="form-group ">
            <label>Color</label>
            <input type="text" class="form-control" name="color" [(ngModel)]="product.color" >
          </div>
            <div class="form-group ">
              <label>Product Type</label>
              <select class="form-control" name="productType" [(ngModel)]="product.productType">
                <option *ngFor="let p of productTypes" [value]="p">{{p}}</option>
              </select>
            </div>
            <div class="form-group">
              <label>Brand</label>
              <input type="text" class="form-control" name="brand" [(ngModel)]="product.brand">
            </div>
            <div class="form-group">
              <label>Price</label>
              <input type="text" class="form-control" name="price" [(ngModel)]="product.price">
            </div>
            <button type="submit" class="btn btn-primary"" >Submit</button>
            <button class="btn btn-default">Reset</button>
        </form>
        </div>
        <div class="col-md-6">
          <label>
            <strong>myForm : JSON</strong>
          </label>
          <pre class="alert alert-dark">{{myForm.value | json}}</pre> 
        </div>
      </div>
    </div>
  </div>

We need to initialize product object when we declare it otherwise when a component is loaded on screen it will throw below error.

The [(ngModel)] syntax is a combination of the syntax for input property binding [] and output event binding ()

The long form of writing the above would be:

<input name="title" [ngModel]="product.title" (ngModelChange)="product.title = $event" >

We have discussed two way to work with Template driven forms. You can use any of the ways.

but when you will work with validation of template driven forms, Two Way Data Binding method is easy to use.

Form validation of Template Driven Form is same as we have discussed in Form Validators chapter for Model Driven Forms. But In Template Driven Forms we need not have use Validators class of @angular/forms.

We can directly specify HTML 5 validators inside the input tag. 

For Example, 

<input type="text" class="form-control" name="title" [(ngModel)]="product.title" required minlength="10">

The same way we will add validations for other template control.

As we have discussed in Form Validators, We use different Form Control State like valid, touched and dirty to display visual feedback on the screen. These are the properties of FormControl.

but in template driven forms we do not create a FormControl object inside the Component. So to access FormControl we can use either of below way.

1. Using .form property of ngForm

We can access the title FormControl of myForm using myForm.form.controls.title. using this object we can access the control state of title FormControl as shown below.

<div class="form-group">
            <label>Title</label>
            <input type="text" class="form-control" name="title" [(ngModel)]="product.title" required minlength="10">
            <div class="alert alert-info" role="alert">
              <strong>Valid ? </strong>{{myForm.form.controls.title?.valid}} <br>
              <strong>Invalid ? </strong>{{myForm.form.controls.title?.invalid}}<br>
              <strong>Touched ? </strong>{{myForm.form.controls.title?.touched}}<br>
              <strong>Untouched ? </strong>{{myForm.form.controls.title?.untouched}}<br>
              <strong>Dirty ? </strong>{{myForm.form.controls.title?.dirty}}<br>
              <strong>Pristine ? </strong>{{myForm.form.controls.title?.pristine}}<br>
            </div>
</div>

2. Using local template reference variable.

As you can see in the above code, to access control state properties of title control we need to go through myForm.form.controls.title.valid. Using ngModel directive we can make this shorter.

as shown below, we are creating a local reference to the title FormControl using ngModel directive.

<input type="text" class="form-control" name="title" [(ngModel)]="product.title" required minlength="10" #title="ngModel">

Now, we can access the control state of title using just title.valid, as shown below

<div class="form-group">
            <label>Title</label>
            <input type="text" class="form-control" name="title" [(ngModel)]="product.title" required minlength="10" #ngModel="title">
            <div class="alert alert-info" role="alert">
              <strong>Valid ? </strong>{{title.valid}} <br>
              <strong>Invalid ? </strong>{{title.invalid}}<br>
              <strong>Touched ? </strong>{{title.touched}}<br>
              <strong>Untouched ? </strong>{{title.untouched}}<br>
              <strong>Dirty ? </strong>{{title.dirty}}<br>
              <strong>Pristine ? </strong>{{title.pristine}}<br>
            </div>
</div>

Both the ways will give the same output as shown below.

Form Control State

Now we can show the Visual feedback same as we have seen in the Form Validators of Model Driven Forms, as shown below

<div class="form-group">
            <label>Title</label>
            <input type="text" class="form-control" name="title" [(ngModel)]="product.title" required minlength="10" #title="ngModel" [ngClass]="{'is-invalid': title?.errors && (title?.touched ||title?.dirty), 'is-valid':title?.valid}">
            <div class="invalid-feedback">
              <strong *ngIf="title?.errors?.required">Title is required</strong>
              <strong *ngIf="title?.errors?.minlength">
                Title length must be greated than {{title?.errors?.minlength.requiredLength}} character.
                 {{title?.errors?.minlength.requiredLength - title?.errors?.minlength.actualLength}} more character required. 
              </strong>
            </div>

</div>
Validator Specific Error Message

The same way we will add the validation for all other controls.

Refer Form Validators chapter for detail information about Angular Form Validation and Validation Styles.

As we have discussed in the model driven form, we can submit the form using ngSubmit directive or on the click event of the Submit button. You can use either way.

We will create one method inside the component. As shown below

import { ProductService } from './../service/product.service';
import { Product } from '../models/product';
...
@Component({
  selector: 'app-add-product-template',
  templateUrl: './add-product-template.component.html',
  styleUrls: ['./add-product-template.component.css']
})
export class AddProductTemplateComponent implements OnInit {
  ...
  product : Product = new Product(); 
  formSubmitted = false;
  ...
  constructor(private productService: ProductService) { }
  addProduct() {
    this.productService.addProduct(product);
    this.formSubmitted = true;
  }
  ...
}

We have used the ProductService to add a product to the server.

Note here, we have not passed the parameter as an argument, because product object is created in component and it is two way mapped to the template form. So we can directly use that object inside any component method.

formSubmitted is the flag we have used to show a message once it becomes true.

We will call this method on the click event of submit button, this button will be disabled until all the controls are valid, as shown below.

<button type="submit" class="btn btn-primary" (click)="addProduct()" [disabled]="myForm.invalid">Submit</button>

We can reset the form as the same way as in the model-driven form. As shown below,

<button class="btn btn-default" (click)="myForm.reset()">Reset</button>

Refer Form Submit chapter for detail information about Angular Form Validation and Validation Styles.

<div class="jumbotron jumbotron-fluid">
  <div class="container">
    <h1 class="display-3">Welcome to Electronic-Shop</h1>
    <p class="lead">THE ONLINE MEGASTORE</p>
    <hr class="my-2">
  </div>
</div>
<div class="container-fluid">
  <app-add-product-template>Loading</app-add-product-template>
</div>
<div class="card">
  <div class="card-body">
    <h4 class="card-title">ADD PRODUCT (Template Driven Form)</h4>

    <div class="alert alert-info alert-dismissible " role="alert" *ngIf="formSubmitted">
      <button type="button" class="close" data-dismiss="alert" aria-label="Close">
        <span aria-hidden="true">&times;</span>
        <span class="sr-only">Close</span>
      </button>
      <strong>Bingo !!! Form Submitted...</strong>
    </div>

    <div class="row">
      <div class="col-md-6">
        <form #myForm="ngForm" novalidate>
          <div class="form-group">
            <label>Title</label>

            <input type="text" class="form-control" name="title" [(ngModel)]="product.title" required minlength="10" #title="ngModel"
              [ngClass]="{'is-invalid': title?.errors && (title?.touched || title?.dirty), 
                                              'is-valid':title?.valid}">
            <div class="invalid-feedback">
              <strong *ngIf="title?.errors?.required">Title is required</strong>
              <strong *ngIf="title?.errors?.minlength">
                Title length must be greated than {{title?.errors?.minlength.requiredLength}} character. {{title?.errors?.minlength.requiredLength
                - title?.errors?.minlength.actualLength}} more character required.
              </strong>
            </div>

          </div>
          <div class="form-group ">
            <label>Model Name</label>
            <input type="text" class="form-control" name="modelName" [(ngModel)]="ngModel">
          </div>
          <div class="form-group ">
            <label>Color</label>
            <input type="text" class="form-control" name="color" [(ngModel)]="product.color" pattern="[a-zA-Z]*" #color="ngModel"
              [ngClass]="{'is-invalid': color.errors && (color.touched || color.dirty)}">
            <div class="invalid-feedback">
              <strong>Only Alphabets are allowed. </strong>
            </div>
          </div>
            <div class="form-group ">
              <label>Product Type</label>
              <select class="form-control" name="productType" [(ngModel)]="product.productType" required #productType="ngModel"
                [ngClass]="{'is-invalid': productType.errors && (productType.touched || productType.dirty), 'is-valid':productType.valid}">
                <option *ngFor="let p of productTypes" [value]="p">{{p}}</option>
              </select>
              <div class="invalid-feedback">
                <strong>Select Product Type.</strong>
              </div>
            </div>
            <div class="form-group">
              <label>Brand</label>
              <input type="text" class="form-control" name="brand" [(ngModel)]="product.brand" #brand="ngModel" required [ngClass]="{'is-invalid': brand.errors && (brand.touched || brand.dirty), 'is-valid':brand.valid}">
              <div class="invalid-feedback">
                <strong>Brand Name is required.</strong>
              </div>
            </div>
            <div class="form-group">
              <label>Price</label>
              <input type="text" class="form-control" name="price" [(ngModel)]="product.price" required min="0" #price="ngModel"
                [ngClass]="{'is-invalid': price.errors && (price.touched || price.dirty), 'is-valid':price.valid}">
              <div class="invalid-feedback">
                <strong *ngIf="price.errors?.required"> Price is required</strong>
                <strong *ngIf="price.errors?.min"> Price should be greated than zero.</strong>
              </div>
            </div>
            <button type="submit" class="btn btn-primary" (click)="addProduct()" [disabled]="myForm.invalid">Submit</button>
            <button class="btn btn-default" (click)="myForm.reset()">Reset</button>
        </form>
        </div>
        <div class="col-md-6">
          <label>
            <strong>myForm : JSON</strong>
          </label>
          <pre class="alert alert-dark">{{myForm.value | json}}</pre>
        </div>
      </div>
    </div>
  </div>
import { ProductService } from './../service/product.service';
import { Product } from './../models/product';
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-add-product-template',
  templateUrl: './add-product-template.component.html',
  styleUrls: ['./add-product-template.component.css']
})
export class AddProductTemplateComponent implements OnInit {

  product: Product = new Product();
  formSubmitted = false;
  productTypes = ['Laptop', 'Mobile' ];
  constructor(public productService: ProductService) { }

  ngOnInit() {
  }

  addProduct() {
    this.productService.addProduct(this.product);
    this.formSubmitted = true;
  }

}
import { MockData } from './../mock-data/mock-product-data';
import { Injectable } from '@angular/core';
import { Product } from '../models/product';
@Injectable()
export class ProductService {
  products: Product[] = [];
  constructor() {
    this.products = MockData.Products;
  }
  getProducts(): Product[] {
    return this.products;
  }
  addProduct(product: Product) {
    this.products.push(product);
  }
  removeProduct(product: Product) {
    let index = this.products.indexOf(product);
    if (index !== -1) {
      this.products.splice(index, 1);
    }
  }
}

In this chapter, we have seen

  • How template driven forms differ with model-driven forms ?
  • Form Directive: ngForm and ngModel
  • Use of Two-way data binding.
  • Template Driven Form Validation
  • Form Submit and Form Reset in Template Driven Forms.