← Back to Tutorials Chapter 6

Forms

Two Approaches to Forms

Angular provides two form-building approaches. Template-driven forms are declarative and placed mostly in the template. Reactive forms are more programmatic, with the form model defined explicitly in the component class. Both approaches handle validation, user input, and submission, but reactive forms offer greater flexibility and testability.

Template-Driven Forms

Template-driven forms rely on directives in the template. You import FormsModule and use ngModel with two-way binding.

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

@Component({...})
export class LoginComponent {
  onSubmit(form: NgForm) {
    console.log('Form submitted:', form.value);
  }
}
<form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm)">
  <input name="email" ngModel required email placeholder="Email">
  <input name="password" ngModel required minlength="6" type="password">
  <button type="submit" [disabled]="loginForm.invalid">Login</button>
</form>

Validation in Template-Driven Forms

Use standard HTML5 validation attributes like required, minlength, maxlength, pattern, and email. Angular automatically tracks validation state and adds CSS classes (ng-valid, ng-invalid, ng-touched, ng-dirty).

<input name="phone" ngModel required pattern="[0-9]{10}" #phone="ngModel">
<div *ngIf="phone.invalid && phone.touched" class="error">
  <span *ngIf="phone.errors?.['required']">Phone is required.</span>
  <span *ngIf="phone.errors?.['pattern']">Enter a valid 10-digit phone.</span>
</div>

Reactive Forms

Reactive forms are built programmatically using FormGroup, FormControl, and FormArray. Import ReactiveFormsModule and define the form model in the component class.

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

@Component({...})
export class ProfileComponent {
  profileForm = new FormGroup({
    firstName: new FormControl('', [Validators.required, Validators.minLength(2)]),
    lastName: new FormControl('', Validators.required),
    email: new FormControl('', [Validators.required, Validators.email]),
    address: new FormGroup({
      street: new FormControl(''),
      city: new FormControl('')
    })
  });

  onSubmit() {
    if (this.profileForm.valid) {
      console.log('Profile saved:', this.profileForm.value);
    }
  }
}
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
  <input formControlName="firstName" placeholder="First Name">
  <input formControlName="lastName" placeholder="Last Name">
  <input formControlName="email" placeholder="Email">
  <div formGroupName="address">
    <input formControlName="street" placeholder="Street">
    <input formControlName="city" placeholder="City">
  </div>
  <button type="submit" [disabled]="profileForm.invalid">Save</button>
</form>

FormBuilder

FormBuilder is a service that provides syntactic sugar for creating form controls and groups:

constructor(private fb: FormBuilder) {
  this.profileForm = this.fb.group({
    firstName: ['', [Validators.required, Validators.minLength(2)]],
    lastName: ['', Validators.required],
    email: ['', [Validators.required, Validators.email]],
    address: this.fb.group({
      street: [''],
      city: ['']
    })
  });
}

Custom Validators

Create custom validation functions for specific business rules:

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

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

Dynamic Forms with FormArray

Use FormArray to manage a dynamic list of form controls:

this.orderForm = this.fb.group({
  items: this.fb.array([this.createItem()])
});

get items() { return this.orderForm.get('items') as FormArray; }

createItem(): FormGroup {
  return this.fb.group({
    product: ['', Validators.required],
    quantity: [1, [Validators.required, Validators.min(1)]]
  });
}

addItem() { this.items.push(this.createItem()); }
removeItem(index: number) { this.items.removeAt(index); }
Angular form validation

Error Messages

Display user-friendly error messages by checking the control's errors object:

<div *ngIf="firstName.invalid && (firstName.dirty || firstName.touched)">
  <p *ngIf="firstName.errors?.['required']">First name is required.</p>
  <p *ngIf="firstName.errors?.['minlength']">
    Minimum {{ firstName.errors?.['minlength'].requiredLength }} characters.
  </p>
</div>

Summary

In this chapter, you explored both template-driven and reactive forms, including validation, FormBuilder, custom validators, dynamic FormArrays, and error message display. Reactive forms give you more control and are preferred for complex form scenarios.

Exercise 6: Build a reactive registration form with firstName, lastName, email, password, confirmPassword, and a dynamic list of phone numbers (FormArray). Add custom validators: passwords must match, email cannot be a disposable domain, and each phone number must be valid. Display inline error messages. Disable the submit button while invalid. Log the form value on submit.