Angular 2 Forms
In this chapter, we will introduce how to build an Angular form using components and templates.
Using Angular templates, we can create various types of forms, such as login forms, contact forms, product detail forms, etc., and we also add data validation to these form fields.
Next, we will implement the form functionality step by step.
Creating a Project
Import the initialized project.
For a complete project creation, refer to: Angular 2 TypeScript Environment Configuration
Or directly download the source code: Click to Download
After extracting, rename the directory to angular-forms
and change "name": "angular-quickstart" to "name": "angular-forms" in the angular-forms/package.json
file.
Once done, we execute cnpm install to load the dependencies.
Creating the Site Model
The following creates a simple model class Site
, which includes three required fields: id
, name
, url
, and an optional field: alexa
.
Create the site.ts
file under the angular-forms/app
directory with the following code:
app/site.ts File:
export class Site {
constructor(
public id: number,
public name: string,
public url: string,
public alexa?: number
) { }
}
In the code below, fields marked as public
are public fields, and alexa
followed by a question mark (?) indicates an optional field.
Creating a Form Component
Each Angular form consists of two parts: an HTML-based template and a code-based component that handles data and user interactions.
Create the site-form.component.ts
file under the angular-forms/app
directory with the following code:
app/site-form.component.ts File:
import { Component } from '@angular/core';
import { Site } from './site';
@Component({
moduleId: module.id,
selector: 'site-form',
templateUrl: 'site-form.component.html'
})
export class SiteFormComponent {
urls = ['www.tutorialpro.org', 'www.google.com',
'www.taobao.com', 'www.facebook.com'];
model = new Site(1, 'tutorialpro.org', this.urls[0], 10000);
submitted = false;
onSubmit() { this.submitted = true; }
// TODO: Remove after completion
get diagnostic() { return JSON.stringify(this.model); }
}
The example imports the Component
decorator and the Site
model.
The @Component
selector "site-form" means we can include this form in a parent template using a <site-form> tag.
The templateUrl
property points to a separate HTML template file named site-form.component.html
.
The diagnostic
property is used to return the JSON representation of the model.
Defining the Root Module of the Application
Modify app.module.ts
to define the root module of the application, specifying the external references and components declared within this module, such as SiteFormComponent
.
Since template-driven forms have their own module, we need to add FormsModule
to the imports
array of this application to use forms.
The app/app.module.ts
file code is as follows:
app/app.module.ts File:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { SiteFormComponent } from './site-form.component';
@NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [ AppComponent, SiteFormComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { SiteFormComponent } from './site-form.component';
@NgModule({
imports: [
BrowserModule,
FormsModule
],
declarations: [
AppComponent,
SiteFormComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Create the Root Component
Modify the root component file app.component.ts
to include SiteFormComponent
.
app/app.component.ts File:
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: '<site-form></site-form>'
})
export class AppComponent { }
Create an Initial HTML Form Template
Create the template file site-form.component.html
with the following code:
app/site-form.component.html File:
<div class="container">
<h1>Website Form</h1>
<form>
<div class="form-group">
<label for="name">Website Name</label>
<input type="text" class="form-control" id="name" required>
</div>
<div class="form-group">
<label for="alexa">Alexa Rank</label>
<input type="text" class="form-control" id="alexa">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
The required
attribute makes this field mandatory; if not set, it is optional.
Enter the following command in the angular-forms directory:
cnpm install bootstrap --save
Open the index.html
file and add the following style link to the <head>
:
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
After running npm start
, access http://localhost:3000/
, and the output will look like this:
Using ngModel for Two-Way Data Binding
Next, we will use ngModel
for two-way data binding, which updates the component's properties by listening to DOM events.
Modify app/site-form.component.html
to use ngModel
for binding our form to the model. The code is as follows:
app/site-form.component.html File:
<div class="container">
<h1>Website Form</h1>
<form>
{{diagnostic}}
<div class="form-group">
<label for="name">Website Name</label>
<input type="text" class="form-control" id="name"
required
[(ngModel)]="model.name" name="name">
</div>
<div class="form-group">
<label for="alexa">Alexa Rank</label>
<input type="text" class="form-control" id="alexa"
[(ngModel)]="model.alexa" name="alexa">
</div>
<div class="form-group">
<label for="url">Website URL</label>
<select class="form-control" id="url"
required
[(ngModel)]="model.url" name="url">
<option *ngFor="let p of urls" [value]="p">{{p}}</option>
</select>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
-
Each input element has an id attribute, which is used by the label element's for attribute to match the label to the corresponding input.
-
Each input element has a name attribute, which is required by Angular's form module to register the control with the form.
Running the above example outputs the following result:
{{diagnostic}} is only used for outputting data during testing.
We can also track the modification status and validity of the form through ngModel, which uses three CSS classes to update the control to reflect the current state.
State | Class if true | Class if false |
---|---|---|
Control has been visited | ng-touched | ng-untouched |
Control value has changed | ng-dirty | ng-pristine |
Control value is valid | ng-valid | ng-invalid |
This allows us to add custom CSS to reflect the form's state.
Create a forms.css file in the angular-forms directory with the following code:
forms.css file:
.ng-valid[required], .ng-valid.required {
border-left: 5px solid #42A948; /* green */
}
.ng-invalid:not(form) {
border-left: 5px solid #a94442; /* red */
}
Open the index.html file and add the following style link to the <head>:
<link rel="stylesheet" href="forms.css">
Modify app/site-form.component.html, as shown below:
app/site-form.component.html file:
<div class="container">
<h1>Website Form</h1>
<form>
{{diagnostic}}
<div class="form-group">
<label for="name">Website Name</label>
<input type="text" class="form-control" id="name"
required
[(ngModel)]="model.name" name="name"
#name="ngModel">
<div [hidden]="name.valid || name.pristine"
class="alert alert-danger">
Website name is required
</div>
</div>
<div class="form-group">
<label for="alexa">Alexa Rank</label>
<input type="text" class="form-control" id="alexa"
[(ngModel)]="model.alexa" name="alexa">
</div>
<div class="form-group">
<label for="url">Website URL</label>
<select class="form-control" id="url"
required
[(ngModel)]="model.url" name="url">
<option *ngFor="let p of urls" [value]="p">{{p}}</option>
</select>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
<option *ngFor="let p of urls" [value]="p">{{p}}</option>
</select>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
By binding the hidden attribute of the div element to the name control's property, we can control the visibility of the "name" field error message.
Remove the data from the name field, and the result is shown as follows:
Add a Website
Next, we create a form for adding websites, and add a button in app/site-form.component.html:
app/site-form.component.html File:
<button type="button" class="btn btn-default" (click)="newSite()">Add Website</button>
Bind the above button event to the component method:
app/site-form.component.ts File:
active = true;
newSite() {
this.model = new Site(5, '', '');
this.active = false;
setTimeout(() => this.active = true, 0);
}
We add an active flag to the component and initialize it to true. When we add a new website, it sets the active flag to false, and then quickly sets it back to true via a quick setTimeout function.
Submit the Form with ngSubmit
We can use Angular's NgSubmit directive to submit the form and bind it to the SiteFormComponent.submit() method via event binding.
<form *ngIf="active" (ngSubmit)="onSubmit()" #siteForm="ngForm">
We define a template reference variable #siteForm and initialize it to "ngForm".
This siteForm variable now references the NgForm directive, which represents the entire form.
The complete code for site-form.component.ts is as follows:
app/site-form.component.ts File:
import { Component } from '@angular/core';
import { Site } from './site';
@Component({
moduleId: module.id,
selector: 'site-form',
templateUrl: 'site-form.component.html'
})
export class SiteFormComponent {
urls = ['www.tutorialpro.org', 'www.google.com',
'www.taobao.com', 'www.facebook.com'];
model = new Site(1, 'tutorialpro.org', this.urls[0], 10000);
submitted = false;
onSubmit() { this.submitted = true; }
// TODO: Remove after completion
get diagnostic() { return JSON.stringify(this.model); }
active = true;
newSite() {
this.model = new Site(5, '', '');
this.active = false;
setTimeout(() => this.active = true, 0);
}
}
The complete code for app/site-form.component.html is as follows:
app/site-form.component.html File:
<div class="container">
<div [hidden]="submitted">
<h1>Website Form</h1>
<form *ngIf="active" (ngSubmit)="onSubmit()" #siteForm="ngForm">
{{diagnostic}}
<div class="form-group">
<label for="name">Website Name</label>
<input type="text" class="form-control" id="name"
required
[(ngModel)]="model.name" name="name"
#name="ngModel">
<div [hidden]="name.valid || name.pristine"
class="alert alert-danger">
Website Name is required
</div>
</div>
<div class="form-group">
<label for="alexa">Alexa Rank</label>
<input type="text" class="form-control" id="alexa"
[(ngModel)]="model.alexa" name="alexa">
</div>
<div class="form-group">
<label for="url">Website URL</label>
<select class="form-control" id="url"
required
[(ngModel)]="model.url" name="url">
<option *ngFor="let p of urls" [value]="p">{{p}}</option>
</select>
</div>
<button type="submit" class="btn btn-default" [disabled]="!siteForm.form.valid">Submit</button>
<button type="button" class="btn btn-default" (click)="newSite()">Add Website</button>
</form>
</div>
<div [hidden]="!submitted">
<h2>Your submitted information is as follows:</h2>
<div class="row">
<div class="col-xs-3">Website Name</div>
<div class="col-xs-9 pull-left">{{ model.name }}</div>
</div>
<div class="row">
<div class="col-xs-3">Website Alexa Rank</div>
<div class="col-xs-9 pull-left">{{ model.alexa }}</div>
</div>
<div class="row">
<div class="col-xs-3">Website URL</div>
<div class="col-xs-9 pull-left">{{ model.url }}</div>
</div>
<br>
<button class="btn btn-default" (click)="submitted=false">Edit</button>
</div>
</div>
In the template, we bind the hidden attribute to the SiteFormComponent.submitted property.
The main form is visible from the start because the submitted property is false, and it will be hidden after submission when the submitted property is true:
submitted = false;
onSubmit() { this.submitted = true; }
The final directory structure is:
The complete example demonstration GIF is as follows: ```