Part 11: Angular Forms – Template-Driven vs Reactive Approaches

Welcome to our comprehensive guide on Angular Forms! Forms are the backbone of most web applications, and Angular provides two powerful approaches to handle them. Let’s explore both in depth.

Two Form Paradigms in Angular

FeatureTemplate-DrivenReactive (Model-Driven)
SetupFormsModuleReactiveFormsModule
Data ModelImplicit (two-way binding)Explicit (FormControl objects)
ValidationDirective-basedFunction-based
TestabilityHarder to testEasier to test
ComplexitySimpler for basic formsBetter for complex forms
Dynamic ControlsNot supported wellFully supported

1. Template-Driven Forms

Ideal for simple forms with basic validation.

Setup

typescript

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

@NgModule({
  imports: [FormsModule]
})

Basic Form Example

html

<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
  <input 
    name="username" 
    ngModel 
    required 
    #username="ngModel">
  
  <div *ngIf="username.invalid && username.touched">
    Username is required
  </div>

  <button [disabled]="userForm.invalid">Submit</button>
</form>

typescript

onSubmit(form: NgForm) {
  console.log(form.value);  // Form data
  console.log(form.valid); // Validation status
}

Key Features

  • Automatic two-way binding with [(ngModel)]
  • Validation through HTML5 attributes + Angular directives
  • Template reference variables (#var) to access controls

2. Reactive Forms

Better for complex, dynamic forms with custom validation.

Setup

typescript

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

@NgModule({
  imports: [ReactiveFormsModule]
})

Basic Form Example

typescript

// component.ts
import { FormBuilder, Validators } from '@angular/forms';

export class UserComponent {
  userForm = this.fb.group({
    username: ['', [Validators.required, Validators.minLength(3)]],
    email: ['', [Validators.required, Validators.email]],
    address: this.fb.group({
      street: [''],
      city: ['']
    })
  });

  constructor(private fb: FormBuilder) {}

  onSubmit() {
    console.log(this.userForm.value);
  }
}

html

<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
  <input formControlName="username">
  
  <div *ngIf="userForm.get('username').invalid && 
              userForm.get('username').touched">
    Username must be at least 3 characters
  </div>

  <div formGroupName="address">
    <input formControlName="street">
    <input formControlName="city">
  </div>

  <button [disabled]="userForm.invalid">Submit</button>
</form>

Form Validation Deep Dive

Template-Driven Validation

html

<input 
  name="email"
  ngModel
  required
  email
  #email="ngModel">

Reactive Form Validation

typescript

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

Custom Validators

typescript

// Shared validator function
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
  return (control: AbstractControl) => {
    const forbidden = nameRe.test(control.value);
    return forbidden ? { forbiddenName: { value: control.value } } : null;
  };
}

// Usage
username: ['', [
  Validators.required,
  forbiddenNameValidator(/admin/i)
]]

Dynamic Forms

Only possible with Reactive Forms:

typescript

addAlias() {
  this.aliases = this.userForm.get('aliases') as FormArray;
  this.aliases.push(this.fb.control(''));
}

html

<div formArrayName="aliases">
  <h3>Aliases</h3>
  <button (click)="addAlias()">Add Alias</button>
  
  <div *ngFor="let alias of aliases.controls; let i=index">
    <input [formControlName]="i">
  </div>
</div>

Form Best Practices

  1. Validation Messages: Show only after user interaction (touched)
  2. Reusable Components: Create input components with validation messages
  3. Form Organization: Break large forms into subcomponents
  4. Debounce Inputs: For search fields (use valueChanges observable)
  5. Form State: Track pristine/dirty status for better UX

Hands-On Exercise

Build a multi-step registration form with:

  1. Personal info (name, email)
  2. Address section (street, city, zip)
  3. Dynamic array for phone numbers
  4. Cross-field validation (password confirmation)
  5. Summary review step

What’s Next?

In Part 12, we’ll explore State Management with RxJS – how to manage application state reactively!

Leave a Comment

Your email address will not be published. Required fields are marked *