There are two ways to build forms in Angular 2, namely model-driven and template-driven.
In this article, we will learn about buildling template-driven form with validation using the latest forms module, then we will talk about how the differences between the new template form and the deprecated one. Please refer to How to Build Model-driven Forms in Angular 2 if you would like to learn about model-driven forms.
Table of Contents
Live demo on template-driven form with latest forms module.
Live demo on template-driven form with deprecated forms module (RC 4): http://plnkr.co/edit/tvALN2?p=preview
Introduction
We will build a form to capture user information based on this interface.
// user.interface.ts
export interface User {
name: string; // required with minimum 5 characters
address: {
street?: string; // required
postcode?: string;
}
}
Here is how the UI will look:
Requirements
- Set postcode default value to 8000;
- Show error message only when:-
- the field is invalid and it’s dirty (the field is touched/edited), or
- the field is invalid and the form is submitted
App Setup
Here's our file structure:
|- app/
|- app.component.html
|- app.component.ts
|- app.module.ts
|- main.ts
|- user.interface.ts
|- index.html
|- styles.css
|- tsconfig.json
In order to use new forms module, we need to npm install @angular/forms
npm package and import the forms module in application module.
$ npm install @angular/forms --save
Here's the module for our application app.module.ts
:
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
imports: [ BrowserModule, FormsModule ], // import forms module here
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
The App Component
Let's move on to create our app component.
// app.component.ts
import { Component, OnInit } from '@angular/core';
import { User } from './user.interface';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'app.component.html',
})
export class AppComponent implements OnInit {
public user: User; // our model
ngOnInit() {
// we will initialize our model here
}
save(model: User, isValid: boolean) {
// check if model is valid
// if valid, call API to save customer
console.log(model, isValid);
}
}
The code is pretty simple and descriptive by itself.
The HTML View
This is how our HTML view will look like.
<div>
<h1>Add user</h1>
<form #f="ngForm" (ngSubmit)="save(f.value, f.valid)" novalidate>
<!-- we will place our fields here -->
<button type="submit">Submit</button>
</form>
</div>
Notes
- The
#f
is the reference variable to the form directive. Refer to Angular official documentation for more details.- Layman explanation:
- We need a way to retrieve the form data(all the values of our form fields like name, address, postcode, etc). Angular provide us a way, form is exported as ngForm. We then assign the form data to our local variable f.
save()
function will be called when we submit the form.- Layman explanation:
f.value
- an object that refer to the all the form field values.f.valid
- a Boolean that indicates whether the form is valid (e.g. name field is mandatory. If user name field is not filled,f.valid
should be false).
Implementation
Now, let's initialize our user model.
// app.component.ts
/* ... */
ngOnInit() {
// we will initialize our form here
this.user = {
name: '',
address: {
street: '',
postcode: '8000' // set default value to 8000
}
};
}
/* ... */
Let's proceed to bind our user model to the view.
<!-- app.component.html -->
...
<form #f="ngForm" novalidate (ngSubmit)="save(f.value, f.valid)">
<!-- we will place our fields here -->
<!--name-->
<div>
<label>Name</label>
<!--bind name to ngModel, it's required with minimum 5 characters-->
<input type="text"
name="name" [(ngModel)]="user.name"
#name="ngModel" required minlength="5">
<!--show error only when field is not valid & it's dirty or form submited-->
<small [hidden]="name.valid || (name.pristine && !f.submitted)">
Name is required (minimum 5 characters).
</small>
</div>
<!--adrress group-->
<div ngModelGroup="address">
<!--street-->
<div>
<label>Street</label>
<input type="text"
name="street" [(ngModel)]="user.address.street"
#street="ngModel" required>
<small [hidden]="street.valid || (street.pristine && !f.submitted)" class="text-danger">
Street is required.
</small>
</div>
<!--postcode-->
<div>
<label>Post code</label>
<input type="text"
name="postcode" [(ngModel)]="user.address.postcode">
</div>
</div>
<button type="submit">Submit</button>
</form>
...
Notes
- Use
ngModel
to bind the model to form control. The binding can be either one way or two way. In our case, we enable two way binding with banana in a box syntax[()]
. - Use
name
attribute to register a form control with the form. E.g. we assign "name" to the name attribute, then when we retrieve thename
field from form value (f.value
). - Use
ngModelGroup
to group element. E.g. address is a group. Under address, there are street and postcode. #street
is the reference variable to the input directive.- Similar to
#f
, we need a way to read the input data. Angular provide us a way, so input is exported as ngModel. - We then assign the input data to our local variable
street
. - With this, we can then use
street.pristine
,street.valid
, etc to check if the street input is dirty, and its validity. - We use these values to show/hide the error message.
- Similar to
What are the differences with deprecated template forms?
There are a few improvements in in new form compared to the deprecated:-
- Remove ngControl: since
ngControl
andngModel
behavior are quite similar (ngControl
has extra validity checking features), it’s possible to combinengControl
intongModel
, additionalname
attribute is required in the new forms module, those that are coming from Angular 1 shouldbe familiar with this. - Rename NgControlGroup to NgModelGroup: since no more
ngControl
, so there will be no moreNgControlGroup
butNgModelGroup
. - Exports renamed to
ngModel
: In deprecated form, control is exported asngForm
, it's confusing as . E.g. previously, for the name field, the syntax is#name = "ngForm"
, now it's#name = "ngModel"
. - Added submitted flag to NgForm: with this improvment, we don’t need to define submitted property in our component.
For reference purpose, I've provided a live demo of the deprecated forms as well so you can see the differences.
Summary
Template forms is getting better with the latest forms module. If you are curious about what are the new form changes, you may refer to Angular form changes proposal for more details.
How about if user is requires to add password and confirm password? How could we handle custom validation in template-driven forms? You might be interested in How to Implement Custom Validator Directive (Confirm Password) in Angular 2 for this use cases.
That's it. Happy coding!
Live demo here on template-driven form with deprecated forms module (RC 4): http://plnkr.co/edit/tvALN2?p=preview
Like this article? Follow @JecelynYeen on Twitter