Telerik blogs

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.

What are Reactive Forms?

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.

Creating Reactive Forms with FormGroups

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 FormGroups as the values.

FormGroups can be nested within other FormGroups.

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.

Create an Array of FromGroups with FormArrays

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.

Conclusion

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.


About the Author

John Au-Yeung

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.

Related Posts

Comments

Comments are disabled in preview mode.