CodeToLive

Angular Reactive Forms

Reactive forms provide a model-driven approach to handling form inputs whose values change over time. They are more robust than template-driven forms, especially for complex scenarios.

Reactive Forms vs Template-Driven Forms

  • Reactive Forms: More explicit, created in component class, immutable, easier to test
  • Template-Driven: Less explicit, created by directives, mutable, familiar to AngularJS users

Setting Up Reactive Forms

First, import ReactiveFormsModule in your module:


// app.module.ts
import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  imports: [
    ReactiveFormsModule
  ]
})
export class AppModule { }
                

Form Controls

The basic building block of reactive forms is FormControl:


// In your component
import { FormControl } from '@angular/forms';

email = new FormControl('');

// Set value
email.setValue('user@example.com');

// Get value
console.log(email.value);

// Listen to value changes
email.valueChanges.subscribe(value => {
  console.log('Email changed:', value);
});
                

Form Groups

Group multiple controls together:


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

profileForm = new FormGroup({
  firstName: new FormControl(''),
  lastName: new FormControl(''),
  email: new FormControl('')
});

// Set values
profileForm.setValue({
  firstName: 'John',
  lastName: 'Doe',
  email: 'john@example.com'
});

// Get values
console.log(profileForm.value);
                

Form Builder

Simplify form creation with FormBuilder:


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

constructor(private fb: FormBuilder) {}

profileForm = this.fb.group({
  firstName: [''],
  lastName: [''],
  email: ['']
});
                

Template Binding

Bind form controls to template elements:


<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
  <label>
    First Name:
    <input type="text" formControlName="firstName">
  </label>
  
  <label>
    Last Name:
    <input type="text" formControlName="lastName">
  </label>
  
  <button type="submit" [disabled]="!profileForm.valid">Submit</button>
</form>
                

Form Validation

Add validation to your forms:


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

profileForm = this.fb.group({
  firstName: ['', Validators.required],
  lastName: ['', Validators.required],
  email: ['', [Validators.required, Validators.email]]
});
                

Display validation messages:


<div *ngIf="profileForm.get('email').invalid && 
            (profileForm.get('email').dirty || profileForm.get('email').touched)">
  <div *ngIf="profileForm.get('email').errors.required">
    Email is required.
  </div>
  <div *ngIf="profileForm.get('email').errors.email">
    Please enter a valid email.
  </div>
</div>
                

Dynamic Forms

Create forms with dynamic controls:


profileForm = this.fb.group({
  firstName: [''],
  lastName: [''],
  addresses: this.fb.array([
    this.fb.control('')
  ])
});

get addresses() {
  return this.profileForm.get('addresses') as FormArray;
}

addAddress() {
  this.addresses.push(this.fb.control(''));
}
                

<div formArrayName="addresses">
  <div *ngFor="let address of addresses.controls; let i=index">
    <input [formControlName]="i">
  </div>
</div>
<button (click)="addAddress()">Add Address</button>
                

Custom Validators

Create your own validation functions:


function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
  return (control: AbstractControl): {[key: string]: any} | null => {
    const forbidden = nameRe.test(control.value);
    return forbidden ? {forbiddenName: {value: control.value}} : null;
  };
}

// Usage
profileForm = this.fb.group({
  firstName: ['', [
    Validators.required,
    forbiddenNameValidator(/admin/i)
  ]]
});
                

Form Submission

Handle form submission:


onSubmit() {
  if (this.profileForm.valid) {
    console.log('Form data:', this.profileForm.value);
    // Send data to server
  } else {
    // Mark all fields as touched to show errors
    this.markFormGroupTouched(this.profileForm);
  }
}

markFormGroupTouched(formGroup: FormGroup) {
  Object.values(formGroup.controls).forEach(control => {
    control.markAsTouched();
    
    if (control instanceof FormGroup) {
      this.markFormGroupTouched(control);
    }
  });
}
                

Value Changes and Status Changes

React to form changes:


// Listen to value changes
this.profileForm.valueChanges.subscribe(value => {
  console.log('Form value changed:', value);
});

// Listen to status changes
this.profileForm.statusChanges.subscribe(status => {
  console.log('Form status:', status);
});
                
Next: HTTP Client