Reactive Forms (FormGroup & FormBuilder)

In contrast to Template-Driven forms, Reactive Forms shift the structural logic strictly into the TypeScript class. This provides much better scalability, easier unit testing, and complete control over dynamic form manipulation (like adding new inputs depending on previous answers).

Importing the Module

You must import the ReactiveFormsModule to gain access to the required directives.

import { ReactiveFormsModule } from '@angular/forms';

@Component({
  ...
  imports: [ReactiveFormsModule]
})

Building the Form (TypeScript)

Instead of using HTML attributes like required, you programmatically define the form structure and its Validators as an Observable tree using the FormBuilder service.

import { FormBuilder, FormGroup, Validators } from '@angular/forms';

export class RegistrationComponent {
  registrationForm: FormGroup;

  constructor(private fb: FormBuilder) {
    // We construct the "shape" of the form programmatically
    this.registrationForm = this.fb.group({
      username: ['', [Validators.required, Validators.minLength(4)]],
      email: ['', [Validators.required, Validators.email]],
      address: this.fb.group({
        street: [''],
        zipCode: ['', Validators.pattern('^[0-9]{5}$')]
      })
    });
  }

  onSubmit() {
    if (this.registrationForm.valid) {
      console.log(this.registrationForm.value);
    }
  }
}

Binding to the HTML

Because the structure is already defined in TypeScript, the HTML becomes incredibly clean. You only need to link the inputs using formControlName.

<!-- We bind the outer form to the [formGroup] directive -->
<form [formGroup]="registrationForm" (ngSubmit)="onSubmit()">
  
  <input type="text" formControlName="username" placeholder="Username">
  
  <!-- Form Validation display is strictly logical -->
  <span *ngIf="registrationForm.get('username')?.touched && registrationForm.get('username')?.invalid">
    Username is required and must be 4 chars!
  </span>

  <input type="email" formControlName="email">

  <!-- Nested form groups map beautifully! -->
  <div formGroupName="address">
    <input type="text" formControlName="street">
    <input type="text" formControlName="zipCode">
  </div>

  <button type="submit" [disabled]="registrationForm.invalid">Register</button>
</form>

Reacting to Changes dynamically

Because Reactive Forms are built on RxJS, you can effortlessly "subscribe" to changes in value or status as the user types, giving you ultimate control over auto-saves or dependent validations.

this.registrationForm.get('username')?.valueChanges.subscribe(val => {
  console.log("User is typing:", val);
});

Conclusion

Reactive Forms are vastly superior for creating robust, enterprise-scale data entry systems. Now that we have the data securely validated, we need to send it to the server. We will do this next using the HTTP Client Configuration.