Post

Mastering Angular Reactive Forms: FormBuilder, Validators, and Dynamic Controls

A comprehensive guide to mastering Angular Reactive Forms with FormBuilder, validation techniques, async validators, dynamic controls, and integration with Angular Material.

Mastering Angular Reactive Forms: FormBuilder, Validators, and Dynamic Controls

1. FormBuilder – Simplifying Form Creation

FormBuilder makes it easier to create forms by reducing boilerplate code.

Without FormBuilder (Verbose)

1
2
3
4
5
6
import { FormGroup, FormControl, Validators } from '@angular/forms';

this.userForm = new FormGroup({
  name: new FormControl('', Validators.required),
  email: new FormControl('', [Validators.required, Validators.email])
});

With FormBuilder (Shorter & Cleaner)

1
2
3
4
5
6
7
8
import { FormBuilder } from '@angular/forms';

constructor(private fb: FormBuilder) {}

this.userForm = this.fb.group({
  name: ['', Validators.required],
  email: ['', [Validators.required, Validators.email]]
});

Use Case: Use FormBuilder for cleaner, more readable code.

2. Listening to Status Changes

statusChanges lets you detect when the form becomes valid, invalid, pending, or disabled.

Example: Listen to Form Status Changes

1
2
3
this.userForm.statusChanges.subscribe(status => {
  console.log('Form status changed:', status);
});

Possible values:

  • VALID
  • INVALID
  • PENDING
  • DISABLED

Use Case: Show submit button only when the form is valid.

3. Custom Async Validator with HTTP API

Validating data with a backend API call (e.g., checking username availability).

Example: Check if Email Exists (API Call)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { AbstractControl, ValidationErrors } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map, debounceTime, switchMap } from 'rxjs/operators';

constructor(private http: HttpClient) {}

emailValidator(control: AbstractControl): Observable<ValidationErrors | null> {
  return control.valueChanges.pipe(
    debounceTime(500), // Wait before making API call
    switchMap(email =>
      this.http.get(`https://api.example.com/check-email/${email}`).pipe(
        map((response: any) => (response.exists ? { emailTaken: true } : null))
      )
    )
  );
}

Use Case: Checking unique fields like email, username, phone numbers, etc.

4. Multi-Step Form with Reactive Forms

Large forms can be split into multiple steps, where each step is a different FormGroup.

Example: Two-Step Registration Form

1
2
3
4
5
6
7
8
9
this.step1 = this.fb.group({
  name: ['', Validators.required],
  email: ['', [Validators.required, Validators.email]]
});

this.step2 = this.fb.group({
  password: ['', [Validators.required, Validators.minLength(6)]],
  confirmPassword: ['', Validators.required]
});

Move Between Steps

1
2
3
4
5
nextStep() {
  if (this.step1.valid) {
    this.currentStep = 2;
  }
}

Use Case: Signup flows, checkout processes, surveys, and complex forms.

5. Dynamic Form Controls with FormArray

Add fields dynamically, like adding multiple phone numbers or skills.

Example: Add/Remove Phone Numbers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
this.userForm = this.fb.group({
  phones: this.fb.array([this.fb.control('')])
});

get phones() {
  return this.userForm.get('phones') as FormArray;
}

addPhone() {
  this.phones.push(this.fb.control(''));
}

removePhone(index: number) {
  this.phones.removeAt(index);
}

HTML

1
2
3
4
5
6
7
<div formArrayName="phones">
  <div *ngFor="let phone of phones.controls; let i = index">
    <input [formControlName]="i" placeholder="Phone Number" />
    <button (click)="removePhone(i)">Remove</button>
  </div>
</div>
<button (click)="addPhone()">Add Phone</button>

Use Case: Adding dynamic items like multiple addresses, emails, phone numbers, or skills.

6. Disable or Enable Form Controls Dynamically

Based on conditions, we can enable or disable form controls dynamically.

Example: Enable/Disable Based on Checkbox

1
2
this.userForm.get('email')?.disable(); // Disables the email field
this.userForm.get('email')?.enable();  // Enables the email field

Use Case: Make fields editable only when needed (e.g., “Edit Profile” mode).

7. Submitting & Sending Form Data to API

After form validation, we submit data to a backend.

Example: Submit Form to API

1
2
3
4
5
6
submitForm() {
  if (this.userForm.valid) {
    this.http.post('https://api.example.com/submit', this.userForm.value)
      .subscribe(response => console.log('Form Submitted:', response));
  }
}

Use Case: Login, registration, profile updates, and form submissions.

8. Reactive Forms with Material UI

Using Angular Material components with ReactiveForms.

Example: Material Input & Checkbox

1
2
3
4
5
6
<mat-form-field>
  <mat-label>Name</mat-label>
  <input matInput formControlName="name" />
</mat-form-field>

<mat-checkbox formControlName="subscribe">Subscribe to Newsletter</mat-checkbox>

Use Case: Better UI/UX with Material Design.

9. Prefilling Form with Data (Edit Mode)

When updating a form, we need to load existing user data.

Example: Prefill User Data

1
2
3
4
this.userForm.patchValue({
  name: 'John Doe',
  email: 'john@example.com'
});

Use Case: Edit user profile, update settings, etc.

10. Reset Form After Submission

After submitting, we may need to reset the form.

Example: Reset Form After Submit

1
this.userForm.reset(); // Resets all values
This post is licensed under CC BY 4.0 by the author.