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:
Workflow
-> Initial form:
Input Name
, Age
and select Nationality
option:
-> Valid data form:
-> Press Submit button to submit form:
– 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 Template Driven Form
Project Setup
– Generate Angular project:
– Generate:
- Customer Component
- Customer Class
– 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>
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 usengModel
directive to bind data with syntax:[(ngModel)]="customer.name"
(customer
is the data model incustomer.component.ts
) class - Added a
name
attribute to theinput
tag. It is a requirement when using[(ngModel)]
in combination with a form. - Declare a template variable for the form:
#customerForm="ngForm"
. The variablecustomerForm
is now a reference to theNgForm
directive which holds the controls on the elements with angModel
directive andname
attribute
– {{customer | json}}
is just evidence for two-way data binding between input
tag and customer
model.
-> Result:
When having any change in the input
tags, we can track the affect of the two-way data binding by the evidence {{customer | json}}
:
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 NOTng-dirty
-> the value of control has been changed. Otherwiseng-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:
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:
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:
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:
– Enter valid data, Submit button is enable:
– Press Submit -> Forward to the detail submitted Customer form:
– Press Edit button -> Go back to 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