Angular 11 Template Driven Form – NgModel for Two-Way Data Binding

Angular 11 Template Driven Form – NgModel for Two-Way Data Binding

In the tutorial, we show how to develop an Angular Form using Template-Driven Form approach with ngModel for two-way data binding, visual feedback and handling error messages.

Related posts:
How to Integrate Bootstrap with Angular (Angular 11)
Angular 11 Component – How to create & integrate New Angular 11 Component
Angular NgFor Repeater Directive – Loop over a Collection (Angular 11)

Angular 11 Form Validation example – Template-driven Forms

Technologies

  • Node.js – version v10.4.0
  • Angular – version 6
  • Bootstrap 4
  • Visual Studio Code – version 1.24.0

Objectives

We build an Angular project as below structure:

angular-6-template-driven-form +project-structure

Workflow

-> Initial form:

angular-6-template-driven-form +customer-form-initial

Input Name, Age and select Nationality option:
-> Valid data form:

angular-6-template-driven-form +customer-form-all-valid

-> Press Submit button to submit form:

angular-6-template-driven-form +submitted-customer

– Press Edit button, The Data Form will be appeared again.
– Delete text of Name input, and select bank value for Nationality input.
-> Error Alert Message appeares:

angular-6-template-driven-form + message-error-for-required-fields

Angular Template Driven Form

Project Setup

– Generate Angular project:

angular-6-template-driven-form +create-project

– Generate:

  • Customer Component
  • Customer Class

angular-6-template-driven-form +generate-customer-component-and-class-customer

– Install Bootstrap 4:

npm install bootstrap jquery --save

-> Configure installed Bootstrap & JQuery in angular.json file:

...
 
"styles": [
  "src/styles.css",
  "node_modules/bootstrap/dist/css/bootstrap.min.css"
],
"scripts": [
  "node_modules/jquery/dist/jquery.min.js",
  "node_modules/bootstrap/dist/js/bootstrap.min.js"
]
 
...

Add the selector app-customer of CustomerComponent to the template app.component.html of AppComponent as below:

<div class="container">
  <div class="row">
    <div class="col-sm-4">
      <app-customer></app-customer>
    </div>
  </div>
</div>

-> Now the project setting is ready for coding.

Forms Module

Template-driven forms are in the module FormsModule, so we need to import FormsModule to AppModule:

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

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

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

@NgModule({
  declarations: [
    AppComponent,
    CustomerComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Data Model

Create Customer data model to capture data-entry from form in file ./customer.ts :

export class Customer {
    constructor(
        public id: number,
        public name: string,
        public nationality: string,
        public age?: number
    ) {}
}

-> age property is optional.

Form Component

Implement CustomerComponent class customer.component.ts as below:

import { Component, OnInit } from '@angular/core';

import { Customer }    from '../customer';

@Component({
  selector: 'app-customer',
  templateUrl: './customer.component.html',
  styleUrls: ['./customer.component.css']
})
export class CustomerComponent {
  nationalities = ['', 'United States', 'United Kingdom', 'Iceland', 'South Korea'];

  customer = new Customer(1, '', '', 23);

  submitted = false;

  onSubmit() { 
    this.submitted = true; 
  }
}

Initial HTML Form Template

Modify template file customer.component.html as below :

<div>
  <h2>Customer Form</h2>
  <form>

    <div class="form-group">
      <label for="name">Name</label>
      <input type="text" class="form-control" id="name">
    </div>

    <div class="form-group">
      <label for="age">Age</label>
      <input type="text" class="form-control" id="age">
    </div>

    <div class="form-group">
      <label for="nationality">Nationality</label>
      <select class="form-control" id="nationality" 
              required>
        <option *ngFor="let nationality of nationalities" [value]="nationality">{{nationality}}</option>
      </select>
    </div>

    <button type="submit" class="btn btn-success">Submit</button>
  </form>
</div>

angular-6-template-driven-form +form-not-yet-setup-angular-control

How to bind data between form in template customer.component.html with customer data model in customer.component.ts?

-> We can use Angular ngModel directive for two-way data binding.

NgModel Directive

Modify the template customer.component.html as below:

<div>
    <h2>Customer Form</h2>
    <form #customerForm="ngForm">
  
      <div class="form-group">
        <label for="name">Name</label>
        <input type="text" class="form-control" id="name" 
               required
               [(ngModel)]="customer.name" name="name">  
      </div>
  
      <div class="form-group">
        <label for="age">Age</label>
        <input type="text" class="form-control" id="age"
                [(ngModel)]="customer.age" name="age">
      </div>
  
      <div class="form-group">
        <label for="nationality">Nationality</label>
        <select class="form-control" id="nationality" 
                required
                [(ngModel)]="customer.nationality" name="nationality">
          <option *ngFor="let nationality of nationalities" [value]="nationality">{{nationality}}</option>
        </select>
      </div>
  
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
</div>

<div>
  {{customer | json}}
</div>

Highlight Points:

  • For each input tag, we use ngModel directive to bind data with syntax: [(ngModel)]="customer.name" (customer is the data model in customer.component.ts) class
  • Added a name attribute to the input tag. It is a requirement when using [(ngModel)] in combination with a form.
  • Declare a template variable for the form: #customerForm="ngForm". The variable customerForm is now a reference to the NgForm directive which holds the controls on the elements with a ngModel directive and name attribute

{{customer | json}} is just evidence for two-way data binding between input tag and customer model.

-> Result:

angular-6-template-driven-form + form-setup-angular-ngForm-two-way-data-binding

When having any change in the input tags, we can track the affect of the two-way data binding by the evidence {{customer | json}} :

angular-6-template-driven-form + two-way-data-binding-angular-ngmodel

Visual Validation Feedback

NgModel directive also controls:

  • when the user touched the control
  • if the value changed
  • if the value became invalid

NgModel uses special Angular CSS classes to update the control:

  • ng-touched & ng-untouched -> control has been visited or NOT
  • ng-dirty -> the value of control has been changed. Otherwise ng-pristine
  • ng-valid & ng-invalid -> the value of control is valid or invalid

Really?

-> We can use an reference variable to check it:

<div class="form-group">
	<label for="name">Name</label>
	<input type="text" class="form-control" id="name" 
		   required
		   [(ngModel)]="customer.name" name="name" 
         #name>  
	{{name.className}}
</div>

-> results:

angular-6-template-driven-form +form-angular-css-control-class

Now we can change the value of ng-valid & ng-invalid for more visual feedback,
-> Modify the style file customer.component.css as below:

.ng-valid[required], .ng-valid.required  {
    border-left: 5px solid rgba(32, 77, 32, 0.623);
}

.ng-invalid:not(form)  {
    border-left: 5px solid rgb(148, 27, 27);
}

-> results:

angular-6-template-driven-form +visual-feedback-using-angular-css

Handle Error Messages

Modify the template customer.component.html as below:

<div>
    <h2>Customer Form</h2>
    <form #customerForm="ngForm">
  
      <div class="form-group">
        <label for="name">Name</label>
        <input type="text" class="form-control" id="name" 
               required
               [(ngModel)]="customer.name" name="name" #name="ngModel">
        <div [hidden]="name.valid || name.pristine"
             class="alert alert-danger">
            Name is required
        </div>

      </div>
  
      <div class="form-group">
        <label for="age">Age</label>
        <input type="text" class="form-control" id="age"
                [(ngModel)]="customer.age" name="age">
      </div>
  
      <div class="form-group">
        <label for="nationality">Nationality</label>
        <select class="form-control" id="nationality" 
                required
                [(ngModel)]="customer.nationality" name="nationality" #nationality="ngModel">
          <option *ngFor="let nationality of nationalities" [value]="nationality">{{nationality}}</option>
        </select>
        <div [hidden]="nationality.valid || nationality.pristine" class="alert alert-danger">
            Nationality is required
        </div>
      </div>
  
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>
  <div>
	{{customer | json}}
  </div>

-> results:

angular-6-template-driven-form +message-error

How to achieve it?

We need a template reference variable to access the input box’s Angular control.
-> With name input tag, we use name variable to reference ngModel: #name="ngModel".

Check below code:

<input type="text" class="form-control" id="name" 
	   required
	   [(ngModel)]="customer.name" name="name" #name="ngModel">
<div [hidden]="name.valid || name.pristine"
	 class="alert alert-danger">
	Name is required
</div>

-> Alert-Message is hidden if the name input’s value is valid or pristine (pristine means the value is not changed since it was displayed).

Form Submission

Up to now, the Submit button is useless. We finally modify the template file CustomerComponent as below:

<div [hidden]="submitted">
    <h2>Customer Form</h2>
    <form  (ngSubmit)="onSubmit()" #customerForm="ngForm">
  
      <div class="form-group">
        <label for="name">Name</label>
        <input type="text" class="form-control" id="name" 
               required
               [(ngModel)]="customer.name" name="name" #name="ngModel">
        <div [hidden]="name.valid || name.pristine"
             class="alert alert-danger">
            Name is required
        </div>
  
      </div>
  
      <div class="form-group">
        <label for="age">Age</label>
        <input type="text" class="form-control" id="age"
                [(ngModel)]="customer.age" name="age">
      </div>
  
      <div class="form-group">
        <label for="nationality">Nationality</label>
        <select class="form-control" id="nationality" 
                required
                [(ngModel)]="customer.nationality" name="nationality" #nationality="ngModel">
          <option *ngFor="let nationality of nationalities" [value]="nationality">{{nationality}}</option>
        </select>
        <div [hidden]="nationality.valid || nationality.pristine" class="alert alert-danger">
            Nationality is required
        </div>
      </div>
  
      <button type="submit" class="btn btn-success" [disabled]="!customerForm.form.valid">Submit</button>
    </form>
</div>
  
<div [hidden]="!submitted">
  <h2>Submitted Customer</h2>
  <hr>
  <div><span class="badge badge-info">Id</span> -> {{customer.id}}</div>
  <div><span class="badge badge-info">Name</span> -> {{customer.name}}</div>
  <div><span class="badge badge-info">Age</span> -> {{customer.age}}</div>
  <div><span class="badge badge-info">Nationality</span> -> {{customer.nationality}}</div>
  <br>
  <button class="btn btn-primary" (click)="submitted=false">Edit</button>
</div>

-> Results:

– Initial Form, Submit is disable:

angular-6-template-driven-form + initial-form-submit-button-hidden

– Enter valid data, Submit button is enable:

angular-6-template-driven-form +submit-button-enable

– Press Submit -> Forward to the detail submitted Customer form:

angular-6-template-driven-form +press-submit-valid-data-results

– Press Edit button -> Go back to data-entry form:

angular-6-template-driven-form +press-edit-button-data-entry-form

How to achieve it?

-> Solution is at the highlight code lines: {1,3,35,39,47}

Revise the form tag:

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

We had defined a reference variable customerForm to ngForm & add ngSubmit directive.
-> Now Submit button already can do the data submission. And It also uses customerForm to control the enable or disable:

<button type="submit" class="btn btn-success" [disabled]="!customerForm.form.valid">Submit</button>

Check the highlight code at lines {1, 39, 47}. -> We use submitted flag to toggle the data-entry form and a div form for showing submitted-data.

Source Code

How to run the below sourcecode:

1. Download Angular-6-Template-Driven-Form.zip file -> Then extract it to Angular-6-Template-Driven-Form folder.
2. Go to Angular-6-Template-Driven-Form folder
3. Run the app by commandline: ng serve --open

-> Source Code:

Angular-6-Template-Driven-Form

5 1 vote
Article Rating
Subscribe
Notify of
guest
833 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments