In this article, we learn about FormGroups in reactive forms and how to use them to validate form fields in your Angular app.
Angular has form validation built in as a feature. This lets us add forms with validation easily. It comes with two types of forms—template-driven forms and 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 FormGroups in reactive forms and how to use them.
Reactive forms objects are objects that provide us with synchronous access to form value data. They are built from observables, so input values and the data value that they bind to are synchronous.
Reactive forms let us bind input fields to model values outside the template explicitly. And we can use Angular’s built-in validators to validate form fields in FormGroups with reactive forms.
We can create FormGroups easily with Angular.
The first step is to import the ReactiveFormsModule
into our app. To do it, in app.module.ts
, we write:
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { ReactiveFormsModule } from "@angular/forms";
import { AppComponent } from "./app.component";
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, ReactiveFormsModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
Next in our component, we add the FormGroup
:
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({
name: new FormControl("", [Validators.required, Validators.minLength(5)]),
birthDate: new FormGroup({
day: new FormControl("", [
Validators.required,
Validators.min(1),
Validators.max(31),
]),
month: new FormControl("", [
Validators.required,
Validators.min(1),
Validators.max(12),
]),
year: new FormControl("", [
Validators.required,
Validators.min(1900),
Validators.max(new Date().getFullYear()),
]),
}),
});
updateProfile() {
this.signUpForm.patchValue({
name: "Jane",
birthDate: {
day: "1",
month: "1",
year: "2000",
},
});
}
onSubmit() {
console.log(this.signUpForm.value);
}
}
We add the FormGroup with the FormGroup
constructor. We use it by passing in an object with the names of the fields as the keys and FormControl
and other FormGroup
s
as the values.
FormGroup
s can be nested within other FormGroup
s.
We add fields by using the FormControl
constructor. The first argument of it is the initial value of the form field. And the second argument is an array of validation rules, which we add by referencing various properties
of the Validators
object.
Validators.required
makes a field required.
Validators.minLength
is a function that sets the minimum acceptable length of the field value.
Validators.min
lets us specify a valid minimum number for a field. Validators.max
lets us specify a valid maximum number for a field.
We can call patchValue
on the FormGroup
to update the value of the fields programmatically.
We can get the input values with the FormGroup’s value
property as it’s done in the submit
method.
Next, in the template we add:
app.component.html
<form [formGroup]="signUpForm" (ngSubmit)="onSubmit()">
<label for="first-name">First Name: </label>
<input id="first-name" type="text" formControlName="name" />
<div
*ngIf="signUpForm.controls.name.invalid && (signUpForm.controls.name.dirty || signUpForm.controls.name.touched)"
>
<div *ngIf="signUpForm.controls.name.errors?.['required']">
Name is required.
</div>
<div *ngIf="signUpForm.controls.name.errors?.['minlength']">
Name must be at least 5 characters long.
</div>
</div>
<br />
<div formGroupName="birthDate">
<h2>Birth Date</h2>
<label for="day">Day: </label>
<input id="day" type="text" formControlName="day" />
<br />
<div
*ngIf="signUpForm.controls.birthDate.controls.day.invalid && (signUpForm.controls.birthDate.controls.day.dirty || signUpForm.controls.birthDate.controls.day.touched)"
>
<div
*ngIf="signUpForm.controls.birthDate.controls.day.errors?.['required']"
>
Day is required.
</div>
<div *ngIf="signUpForm.controls.birthDate.controls.day.errors?.['min']">
Invalid day.
</div>
</div>
<label for="month">Month: </label>
<input id="month" type="text" formControlName="month" />
<br />
<label for="year">Year: </label>
<input id="year" type="text" formControlName="year" />
<br />
<button type="button" (click)="updateProfile()">Update Profile</button>
<button type="submit" [disabled]="!signUpForm.valid">Submit Profile</button>
</div>
</form>
to bind the FormGroup to the form.
We set the [formGroup]
to signUpForm
to bind the signUpForm
FormGroup to this form. We access fields within the FormGroup with the signUpForm
variable.
The invalid
property is a boolean value that lets us check for validity. dirty
is another boolean property that lets us check if the form’s fields have been manipulated or not.
We can access any errors from the rules with the errors
property. It’s an object with key-value pairs with the rules as the keys and the values for the rules as the values.
Also, we have to set the formControlName
attribute to the name of the field as specified when we declared the FormGroup
to bind the HTML input field to the FromGroup field with the same
name and check for the form validation rules specified.
It also binds the input value to the FormGroup property with the name of the field as the key name.
When we click updateProfile
, patchValue
is called on the FormGroup to update the fields.
We can create an array of FormGroups with FormArrays. This way, we can repeatedly render the same FormGroup.
To do this, we write:
app.component.ts
import { Component } from "@angular/core";
import { FormGroup, FormControl, FormArray } from "@angular/forms";
import { Validators } from "@angular/forms";
const createFormGroupObj = () => ({
name: new FormControl("", [Validators.required, Validators.minLength(5)]),
birthDate: new FormGroup({
day: new FormControl("", [
Validators.required,
Validators.min(1),
Validators.max(31),
]),
month: new FormControl("", [
Validators.required,
Validators.min(1),
Validators.max(12),
]),
year: new FormControl("", [
Validators.required,
Validators.min(1900),
Validators.max(new Date().getFullYear()),
]),
}),
});
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
})
export class AppComponent {
signUpForm = new FormGroup(createFormGroupObj());
signUpFormArray = new FormArray([this.signUpForm]);
addSignUpForm() {
const signUpForm = new FormGroup(createFormGroupObj());
this.signUpFormArray.push(signUpForm);
}
onSubmit() {
console.log(this.signUpFormArray.value);
}
}
to add the signUpFormArray
, which is an instance of the FormArray
constructor. We initialize it with an array with one signUpForm
FormGroup inside.
Also, we add the addSignUpForm
method that calls signUpFormArray.push
to add another signUpForm
FormGroup to the FormArray.
And we get all the form fields values from the FormArray with the value
property.
We have to pass in separate objects into FormGroup
so each FromGroup has separate fields. To do this, we create the createFormGroupObj
to return an object with the properties we pass into
FormGroup
, which are the fields for each form in the FormArray.
Next in the component template, we add:
app.component.html
<div *ngFor="let signUpForm of signUpFormArray.controls">
<h1>Person</h1>
<form [formGroup]="signUpForm">
<label for="first-name">First Name: </label>
<input id="first-name" type="text" formControlName="name" />
<div
*ngIf="
signUpForm.controls.name.invalid &&
(signUpForm.controls.name.dirty || signUpForm.controls.name.touched)
"
>
<div *ngIf="signUpForm.controls.name.errors?.['required']">
Name is required.
</div>
<div *ngIf="signUpForm.controls.name.errors?.['minlength']">
Name must be at least 5 characters long.
</div>
</div>
<br />
<div formGroupName="birthDate">
<h2>Birth Date</h2>
<label for="day">Day: </label>
<input id="day" type="text" formControlName="day" />
<br />
<div
*ngIf="
signUpForm.controls.birthDate.controls.day.invalid &&
(signUpForm.controls.birthDate.controls.day.dirty ||
signUpForm.controls.birthDate.controls.day.touched)
"
>
<div
*ngIf="
signUpForm.controls.birthDate.controls.day.errors?.['required']
"
>
Day is required.
</div>
<div *ngIf="signUpForm.controls.birthDate.controls.day.errors?.['min']">
Invalid day.
</div>
</div>
<label for="month">Month: </label>
<input id="month" type="text" formControlName="month" />
<br />
<label for="year">Year: </label>
<input id="year" type="text" formControlName="year" />
</div>
</form>
</div>
<button type="button" (click)="addSignUpForm()">Add Person</button>
<button type="submit" (click)="onSubmit()" [disabled]="!signUpFormArray.valid">
Submit Profile
</button>
to render the signUpFormArray
FormArray.
The only changes we made are rendering FormArray by accessing the FormGroups in the FormArray with signUpFormArray.controls
.
The validation values are accessed the same way as a single form.
Angular has form validation built in as a feature. This lets us add forms with validation easily with reactive forms
Reactive forms work by letting us create form objects and link them to forms, which lets us add validation. Reactive forms let us bind input values to properties in reactive form objects so we can access input values and validate them.
We can create a group of form fields with FormGroups. And we can group FormGroups together with FormArrays.
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.