Forms are an essential part of many web applications, being the most common way to enter and edit text-based data. Front-end JavaScript frameworks such as Angular, often have their own idiomatic ways of creating and validating forms that you need to get to grips with to be productive.
Angular allows you to streamline this common task by providing two types of forms that you can create:
In this article, we’ll make a simple example form with each method to see how it’s done.
You do not need to know all the details of how to create an Angular application to understand the framework’s usefulness when it comes to forms. However, if you want to get a better grasp of Angular, you can take a look at this SitePoint article series on building a CRUD app with Angular.
We will use Bootstrap in this tutorial. It is not an integral part of an Angular application, but it will help us streamline our efforts even further by providing ready-made styles.
This is how you can add it to your application:
Open the command prompt and navigate to the folder of your project
Type npm install [email protected]
. This will add the latest version of bootstrap to the project
Edit the .angular-cli.json
file and add a link to the Bootstrap CSS file
"apps": [
"styles": [
"../node_modules/bootstrap/dist/css/bootstrap.css"
]
]
We will not use the Bootstrap JavaScript file in this application.
Both template-driven Forms and Reactive Forms require the FormsModule
. It should be added to the application in app.module
:
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [
BrowserModule,
FormsModule
]
})
Let us assume you want to create a simple form as quickly as possible. For example, you need a company registration form. How can you create the form?
The first step is to create the <form>
tag in your view.
<form #companyForm="ngForm">
We need to modify this tag in two ways in order to submit the form and use the information from the input fields in our component:
ngForm
directive.ngSubmit
event to a method we will create in our component<form #companyForm="ngForm" (ngSubmit)="submitCompany(companyForm.form);">
We will create the submitCompany
method in the component a bit later. It will be called when the form is submitted and we will pass it the data from the form via companyForm.form
.
We also need a submit button, regardless of the content of the form. We will use a few Bootstrap classes to style the button. It is good practice to disable the button before all the data validation requirements are met. We can use the template variable we created for the form to achieve this. We will bind the disabled property to the valid property of the companyForm
object. This way the button will be disabled if the form is not valid.
<button class="btn btn-primary" [disabled]="!companyForm.valid">Submit</button>
Let us assume our simple form will have two fields – an input field for the name of the company and a drop-down field for the company’s industry.
First, we create an input field for the name:
<input type="text"
class="form-control"
name="company-name">
Right now we have a standard input with the type, name and class attributes. What do we need to do to use the Angular approach on our input?
We need to apply the ngModel
directive to it. Angular will create a control object and associate it with the field. Essentially, Angular does some of the work for you behind the scenes.
This is a good time to mention that ngModel
requires the input field to have a name or the form control must be defined as standalone in ngModelOptions
. This is not a problem because our form already has a name. Angular will use the name attribute to distinguish between the control objects.
In addition, we should specify a template variable for the input: #nameField
in this case. Angular will set nameField
to the ngModel
directive that is applied to the input field. We will use this later for the input field’s validation. This variable will also allow us to perform an action based on the value of the field while we are typing in it.
Now our input looks like this:
<input type="text"
class="form-control"
name="company-name"
ngModel
#nameField="ngModel">
It is almost the same, but with a few key changes.
Let us assume we want the company name field to be required and to have a minimum length of 3 characters. This means we have to add the required
and minlength
attributes to our input:
<input type="text"
class="form-control"
name="company-name"
ngModel
#nameField="ngModel"
required
minlength="3">
Sounds simple enough, right? We will also need to display an error message if any of these two requirements are not met. Angular allows us to check the input’s value and display the appropriate error message before the form is submitted.
We can perform such a check while the user is typing in the form. First of all, it is a good idea to display an error only after the user has started to interact with the form. There is no use in displaying an error message right after we load the page. This is why we will insert all the error messages for this input inside the following div:
<div *ngIf="nameField.touched && nameField.errors"></div>
The ngIf
directive allows us to show the div only when a specific condition is true. We will use the nameField
template variable again here because it is associated with the input. In our case, the div will be visible only, if the input has been touched and there is a problem with it. Alright, what about the error messages themselves?
We will place another div inside the aforementioned one for each error message we want. We will create a new div for the error message and use the nameField
template variable again:
<div class="alert alert-danger"
*ngIf="nameField.errors.required">
The company name is required
</div>
We are using the “alert alert-danger” bootstrap classes to style the text field. The nameField
variable has the property errors
, which contains an object with key-value pairs for all the current errors. The ngIf
directive allows us to show this error message only when the ‘required’ condition is not met. We will use the same approach for the error message about the minimum length.
<div class="alert alert-danger"
*ngIf="nameField.errors.minlength">
The company name should be at least 3 characters long
</div>
This div will be visible only when the minlength
requirements are not met. here we can make the error message a little bit more dynamic.
Currently, we have specified the minimum length in two locations – in the input’s attribute and the text field. We can improve this by replacing the hardcoded “3” with the requiredLength
property of the minlength
object like so:
<div class="alert alert-danger"
*ngIf="nameField.errors.minlength">
The company name should be at least {{ nameField.errors.minlength.requiredLength }} characters long
</div>
This way the number of the minimum length in the error message will depend on the input’s minlength
attribute.
Now, we will do the same thing with the dropdown field for the company ‘s industry:
<select class="form-control"
name="company-industry"
ngModel
#industryField="ngModel"
required>
We will create a list of the options for the dropdown in the component associated with this view in order to avoid hard-coding values in the HTML.
export class ContactFormComponent implements OnInit {
industries = [
{id: 1, name: "Agriculture"},
{id: 2, name: "Manufacturing"},
{id: 3, name: "Energy"},
{id: 4, name: "Transportation"},
{id: 5, name: "Finance"}
];
}
Now we can list all the options in the view via the ngFor
directive. It will create an option tag for every element in the industries
array from the component.
<option *ngFor="let industry of industries"
[value]="industry.id">
{{ industry.name }}
</option>
The validation for this field is quite easy and similar to that for the company name field:
<div class="alert alert-danger"
*ngIf="industryField.touched && !industryField.valid">
The industry is required
</div>
Now our form is ready for submission. Earlier we bound the ngSubmit
event to a method called submitCompany
; let’s go to the component and add that now:
export class ContactFormComponent implements OnInit {
submitCompany(form){
console.log(form.value);
alert("The form was submitted");
form.reset();
}
}
The form
parameter will contain all the data from the form. On the other hand, form.value
will contain just an object with the values of the fields in the form.
Here I will just log the result in the console, but you can do whatever you want with it. I have added an alert with a message to inform the user the form was submitted. This is not required, but it is a good practice to show some sort of notification. form.reset()
will reset the form to its initial state after submission, which means the fields will be emptied.
The other kind of form you can create is a reactive form, which allows you to explicitly create control objects for the form fields yourself. This approach is a good choice when you are building a more complex form and you want to have more control over its behavior.
Let us assume that we need to create an account registration form, which will have two fields for an email and a password. We will use Bootstrap to style this form as well.
The first step is to import the ReactiveFormsModule
class in app.module
because it is necessary for all reactive forms:
import { ReactiveFormsModule } from "@angular/forms";
@NgModule({
imports: [
ReactiveFormsModule
]
})
Then, we need to import the FormGroup
and FormControl
classes in the component for our page in order to explicitly define our control objects:
import { FormGroup, FormControl } from "@angular/forms";
Now we should create an instance of the FormGroup
class and specify all the fields in our form. To put it simply, we will list key-value pairs. The keys will be the names of the fields and the values will be the form objects.
accountForm = new FormGroup({
email: new FormControl(),
password: new FormControl();
Next, we should create the form. We will once again need the <form>
tag. We will add the FormGroup
directive to it and associate the HTML form with the accountForm
form group object we created in the component:
<form [formGroup]="accountForm"></form>
Next, we will create the email input field. We will apply the formControlName
directive to it and set it to the corresponding key in the list of controls we created in the components, email
.
<input type="text"
class="form-control"
id="email"
formControlName="email">
We will do the same for the password field:
<input type="text"
id="password"
class="form-control"
formControlName="password">
The next step is to add validation to the form. We will not use any HTML attributes like “required” as with the template-driven forms. Instead, we have to assign all the validators when we create the form control objects.
We will go back to the component where we defined our accountForm
. All the validator methods for reactive forms are defined in the Validators
class, which we have to import:
import { FormGroup, FormControl, Validators } from "@angular/forms";
Then we will assign the validators to the controls in our controller. The format is the following :
form = new FormGroup({
fieldname: new FormControl(
initial value,
synchronous validators,
asynchronous validators)
});
Let us assume that both the email and the password fields will be required. We should also check if the email is valid. In addition, the password should contain at least one uppercase letter, one lowercase letter, and one number. Thus, we will use the required
and pattern
validators from the Validators
class for both fields. We will leave their initial values as an empty string.
form = new FormGroup({
email: new FormControl("",
[Validators.required,
Validators.pattern('[a-zA-z0-9_\.]+@[a-zA-Z]+\.[a-zA-Z]+')]),
password: new FormControl("",
[Validators.required,
Validators.pattern('^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{8,}/div>)])
});
Now we need to go to the template and add the validation messages. We will do this in the same way we did it with with the template-driven forms. However, we will access the control objects in a different way. In our component we can define a property that gives us access to the control in the form like so:
get email(){
return this.accountForm.get("email");
}
We can access this property in our template. This means that instead of writing this.accountForm.get("email")
every time we want to specify a validation message, we can use just email
.
<div *ngIf="email.touched && email.errors">
<div class="alert alert-danger" *ngIf="email.errors.required">
The email is required
</div>
</div>
<div *ngIf="email.errors">
<div class="alert alert-danger" *ngIf="email.errors.pattern">
The email is not valid
</div>
</div>
This way, the message “The email is required” will appear after the user touched the form and left it empty, while the message “The email is not valid” will appear as the user is typing. We can display the validation messages for the password field in the same way.
Let us move on to submitting our reactive form. Firstly, we can disable the submit button in a similar way to the one we used with the template-driven form:
<button class="btn btn-primary" type="submit"
[disabled]="!accountForm.valid">Sign up</button>
We also need to bind the ngSubmit
event to a function, which will be called on submit.
<form [formGroup]="accountForm" (ngSubmit)="signup()">
Then we need to define that function in the controller:
signup(){
console.log(this.accountForm.value);
alert('The form was submitted');
this.accountForm.reset();
}
For now, we will show the submitted data in the console. We will clear the form fields after we display a confirmation message.
It will be great if we can check if the email the user is trying to submit is already in use. We can perform such a check even as the user is typing in if we use an asynchronous validator.
We will use a fake API for the purposes of this demo – JSON Placeholder. This is a useful tool for testing an application because it provides various kinds of data. For example, it can provide a list of users with emails, which we will pretend is the list of existing users for our demo application. You can send get and post requests to it just as you would with a real API.
We will create a service in our application that connects to this JSON API, and attaches an asynchronous validator to the email field. This way, we will be able to check if the email is already in use.
First, we will create the service. We can do that via the Angular CLI
ng g service server.service
Then, we have to add the service to app.module
so that we can use it in the application:
import { ServerService } from "./server.service";
@NgModule({
providers: [
ServerService
],
bootstrap: [AppComponent]
})
In our service, we need to import the Injectable
, Http
and Observable
classes as well as the map
and filter
RxJS operators. Then we will specify the URL to our test API. After we get the results we will filter them to see if there is a user with an email that matches the one the user typed, which we will pass to it when we perform the request.
@Injectable()
export class ServerService {
private url = "http://jsonplaceholder.typicode.com/users";
constructor(private http: Http) { }
checkUsers(email: string) {
return this.http
.get(this.url)
.map(res => res.json())
.map(users => users.filter(user => user.email === email))
.map(users => !users.length);
}
}
Now we have to create the validator, which will use this service to check the email. We will create a new typescript file, custom.validators.ts. This will allow us to separate our code in a more effective way and reuse the validator. There we will import the AbstractControl
and ValidationErrors
classes as well as the ServerService
.
import { AbstractControl, ValidationErrors } from '@angular/forms';
import { ServerService } from './server.service';
export class Customvalidators{
static checkDuplicateEmail(serverService: ServerService) {
return (control: AbstractControl) => {
return serverService.checkUsers(control.value).map(res => {
return res ? null : { duplicateEmail: true };
});
};
}
}
We create an instance of our serverService
and call the checkUsers
method we created in it. Custom validators are supposed to return null
if everything is OK, or an object with key-value pairs that describe the error otherwise.
Now we will go to our component to apply the asynchronous validator to the email field. We will have to import the ServerService
into the component as well and create an instance of it in order to perform the request to our test API.
import { ServerService } from "../server.service";
constructor(private serverService: ServerService){
}
accountForm = new FormGroup({
email: new FormControl("", synchronous validators,
Customvalidators.checkDuplicateEmail(this.serverService))
});
The only thing left to do is add a validation message
<div *ngIf="email.errors">
<div class="alert alert-danger" *ngIf="email.errors.duplicateEmail">
The email is already in use
</div>
</div>
**Recommended Courses: **
Unit Testing AngularJS: Build Bugfree Apps That Always Work!
☞ http://on.codetrick.net/SJICuOzxf
Build Enterprise Applications with Angular 2
☞ http://on.codetrick.net/HkIRd_fgf
Angular Router Course with E-Book Included (Angular 2 and Angular 4)
☞ https://goo.gl/638Jiw
Angular4 for beginners: Learn from scratch
☞ https://goo.gl/YPLfqU
☞ Angular Tutorial - Learn Angular from Scratch
☞ Angular and Nodejs Integration Tutorial
☞ Learn JavaScript - Become a Zero to Hero
☞ JavaScript Programming Tutorial Full Course for Beginners
☞ Learn Angular 8 from Scratch for Beginners - Crash Course
☞ Machine Learning Zero to Hero - Learn Machine Learning from scratch