One of two types of built-in form validation from Angular, reactive forms allow us to create form objects and link them to forms, which lets us add validation.
Angular has form validation built in as a feature. This lets us add forms with validation easily. It comes with two types of forms. One is template-driven forms and the other is reactive forms.
Template-driven forms let us add directives to bind input values to reactive values and also to add form validation.
Reactive forms work by letting us create form objects and link them to forms, which lets us add validation. It binds input values to properties in reactive form objects so we can access input values and validate them.
In this article, we will look at what reactive forms are and how to use them.
Reactive forms objects are objects that provide us with synchronous access to form value data. They’re built from observables, so input values and the data value that they bind to are synchronous. Each change in a form state returns a new state.
This is different from template-driven forms since changes are asynchronous in template-driven forms.
The synchronous nature of reactive forms makes testing reactive forms easier than template-driven forms.
Reactive forms should be used in most Angular apps.
The only benefit that template-driven forms have over reactive forms is that the syntax of template-driven forms is closer to Angular.js forms. Therefore, using template-driven forms would make migrating from Angular.js apps to Angular easier.
Other than that, there isn’t much benefit to using template-driven forms in our Angular apps. So unless we are migrating Angular.js apps to Angular, we should stick with reactive forms.
The immutability and the predictability because of the synchronous updates of reactive forms makes development much easier. In addition, reactive forms let us define the form’s structure explicitly so it’s easy to understand and better for scalability.
To discover the usefulness of reactive forms, we will create one.
We start by importing the ReactiveFormsModule
by writing:
app.module.ts
import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";
import { ReactiveFormsModule } from "@angular/forms";
import { AppComponent } from "./app.component";
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, ReactiveFormsModule],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}
We import ReactiveFormsModule
into our Angular module so we can create reactive forms in the components in AppModule
.
Then we create a form group object in a component by writing:
app.component.ts
import { Component } from "@angular/core";
import { FormGroup, FormControl } from "@angular/forms";
import { Validators } from "@angular/forms";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
signUpForm = new FormGroup({
firstName: new FormControl("", [
Validators.required,
Validators.minLength(5)
]),
lastName: new FormControl("", [
Validators.required,
Validators.minLength(5)
]),
address: new FormGroup({
street: new FormControl("", [
Validators.required,
Validators.minLength(5)
]),
city: new FormControl("", [Validators.required, Validators.minLength(5)]),
region: new FormControl("", [Validators.required])
})
});
updateProfile() {
this.signUpForm.patchValue({
firstName: "Jane",
lastName: "Smith",
address: {
street: "123 1st Street"
}
});
}
onSubmit() {
console.log(this.signUpForm.value);
}
}
We added a FormGroup
object into our AppComponent
. We created it by adding fields into the object we passed into FormGroup
.
The object has the field names as property keys, which we will use to associate with input fields so we can bind input values to component values and provide validation for each field.
The values are FormControl
objects. We pass in the initial value and an array of validators into FormControl
respectively.
Angular provides a variety of form field validators in the Validators
object. We make a form field required with Validators.required
and enforce a minimum length of input values with
Validators.minLength
.
We can manipulate forms in the component’s code. In the updateProfile
method, we call this.signUpForm.patchValue
to update the input value of the firstName
,
lastName
and address.street
fields. And we get the input values of all the fields by using the this.signUpForm.value
property.
Next, in our template, we add our form HTML code. In app.component.html
, we write:
<form [formGroup]="signUpForm" (ngSubmit)="onSubmit()">
<label for="first-name">First Name: </label>
<input id="first-name" type="text" formControlName="firstName" />
<div
*ngIf="signUpForm.controls.firstName.invalid && (signUpForm.controls.firstName.dirty || signUpForm.controls.firstName.touched)"
>
<div *ngIf="signUpForm.controls.firstName.errors?.required">
First name is required.
</div>
<div *ngIf="signUpForm.controls.firstName.errors?.minlength">
First name must be at least 5 characters long.
</div>
</div>
<br />
<label for="last-name">Last Name: </label>
<input id="last-name" type="text" formControlName="lastName" />
<div
*ngIf="signUpForm.controls.lastName.invalid && (signUpForm.controls.firstName.dirty || signUpForm.controls.lastName.touched)"
>
<div *ngIf="signUpForm.controls.lastName.errors?.required">
Last name is required.
</div>
<div *ngIf="signUpForm.controls.lastName.errors?.minlength">
Last name must be at least 5 characters long.
</div>
</div>
<br />
<div formGroupName="address">
<h2>Address</h2>
<label for="street">Street: </label>
<input id="street" type="text" formControlName="street" />
<br />
<div
*ngIf="signUpForm.controls.address.controls.street.invalid && (signUpForm.controls.address.controls.street.dirty || signUpForm.controls.address.controls.street.touched)"
>
<div *ngIf="signUpForm.controls.address.controls.street.errors?.required">
Last name is required.
</div>
<div
*ngIf="signUpForm.controls.address.controls.street.errors?.minlength"
>
Last name must be at least 5 characters long.
</div>
</div>
<label for="city">City: </label>
<input id="city" type="text" formControlName="city" />
<br />
<label for="region">Region: </label>
<input id="region" type="text" formControlName="region" />
<br />
<button type="button" (click)="updateProfile()">Update Profile</button>
<button type="submit" [disabled]="!signUpForm.valid">Submit Profile</button>
</div>
</form>
To associate the form group object to an HTML form, we set the [formGroup]
attribute to the name of the FormGroup
instance we defined in our component.
We set (ngSubmit)
to onSubmit()
to call onSubmit
when we submit the form.
And we set the formControlName
attribute to the name of the FormControl
instance to associate the form control object to the form field. This will bind the input value to the form control
object’s value and also lets us add validation by checking properties in the form control object.
To get the validation states from form control objects, we use the signUpForm.controls
property. We use signUpForm.controls.firstName
to get the validation state of the firstName
form control. And we do the same with lastName
.
We get the validation errors from the firstName
form control with signUpForm.controls.firstName.errors
.
To check whether the whole form is valid, we use the signUpForm.valid
property.
To associate a group of fields to a form group, we set the formGroupName
attribute as we did with the div to associate the fields in the form group to the address
form group.
To get the validation state of nested fields, we keep using the controls
property.
We get the street
field with signUpForm.controls.address.controls.street
. And the validation states are in that object.
With reactive forms, we can bind input values to component property values and add validation easily by creating a single object. Also, we can update input values with that object with patchValue
. And we associate HTML
input fields with the form control objects with a few attributes.
All these operations are synchronous, so the workflow is predictable.
Validators are explicitly added to form controls so we can read and change validation of each field easily. And Angular provides many validators for reactive forms so we can use many of them instead of writing our own validation logic. Nested form fields are also easy to add since we can nest form groups.
Therefore, there is not much of a use case for using template-driven forms or creating our own form validation solutions.
Reactive forms come standard with Angular so we do not have to install anything to use it.
Angular has form validation built in as a feature. This lets us add forms with validation easily. It comes with two types of forms—reactive and template-driven forms.
Template-driven forms let us add directives to bind input values to reactive values and also to add form validation.
Reactive forms work by letting us create form objects and link them to forms, which lets us add validation. It binds input values to properties in reactive form objects so we can access input values and validate them. It also lets us add form validation in an explicit manner and we can use the provided validators to add validations to fields.
From groups let us organize forms fields into groups and we can nest form groups easily. We can also get and set form values easily in a synchronous manner with reactive forms.
Therefore, reactive forms are better than template-driven forms or creating our own form field binding and validation solutions in almost all cases.
John Au-Yeung is a frontend developer with 6+ years of experience. He is an avid blogger (visit his site at https://thewebdev.info/) and the author of Vue.js 3 By Example.