Tutorial Series Part 2: Forms, Validation, and UX Best Practices

May 10, 2023

In the previous part of this series, we covered building a multi-page Angular app with routing, lazy loading, and route guards. Now, we’ll dive into forms—the heart of user interaction in many web apps. We’ll focus on typed reactive forms, effective validation techniques, and UX best practices to build user-friendly and robust forms.


Why Reactive Forms?

Reactive forms in Angular provide a model-driven approach to handling form inputs. Compared to template-driven forms, reactive forms offer:

  • More control over validation and form state
  • Easier unit testing
  • Strong support for typed forms (introduced in Angular 14+)

Setting Up Typed Reactive Forms

Let’s start by creating a simple user registration form with typed reactive forms.

Step 1: Define the form model interface

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

interface RegistrationForm {
  username: FormControl<string>;
  email: FormControl<string>;
  password: FormControl<string>;
  confirmPassword: FormControl<string>;
}

Step 2: Initialize the form group

this.registrationForm = new FormGroup<RegistrationForm>({
  username: new FormControl('', { validators: [Validators.required, Validators.minLength(3)] }),
  email: new FormControl('', { validators: [Validators.required, Validators.email] }),
  password: new FormControl('', { validators: [Validators.required, Validators.minLength(8)] }),
  confirmPassword: new FormControl('', { validators: [Validators.required] }),
});

Step 3: Add the template

<form [formGroup]="registrationForm" (ngSubmit)="onSubmit()">
  <label>
    Username:
    <input formControlName="username" />
  </label>
  <div *ngIf="registrationForm.controls.username.invalid && registrationForm.controls.username.touched">
    Username is required (min 3 chars).
  </div>

  <label>
    Email:
    <input formControlName="email" />
  </label>
  <div *ngIf="registrationForm.controls.email.invalid && registrationForm.controls.email.touched">
    Enter a valid email address.
  </div>

  <label>
    Password:
    <input type="password" formControlName="password" />
  </label>
  <div *ngIf="registrationForm.controls.password.invalid && registrationForm.controls.password.touched">
    Password is required (min 8 chars).
  </div>

  <label>
    Confirm Password:
    <input type="password" formControlName="confirmPassword" />
  </label>
  <div *ngIf="registrationForm.controls.confirmPassword.invalid && registrationForm.controls.confirmPassword.touched">
    Please confirm your password.
  </div>

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

Custom Validator Directive: Password Match

A common validation requirement is to ensure password and confirmPassword fields match.

Creating the validator function

import { AbstractControl, ValidatorFn } from '@angular/forms';

export function passwordMatchValidator(): ValidatorFn {
  return (control: AbstractControl) => {
    const password = control.get('password');
    const confirmPassword = control.get('confirmPassword');

    if (!password || !confirmPassword) return null;

    return password.value === confirmPassword.value ? null : { passwordMismatch: true };
  };
}

Applying it to the form group

this.registrationForm = new FormGroup<RegistrationForm>({
  username: new FormControl('', { validators: [Validators.required, Validators.minLength(3)] }),
  email: new FormControl('', { validators: [Validators.required, Validators.email] }),
  password: new FormControl('', { validators: [Validators.required, Validators.minLength(8)] }),
  confirmPassword: new FormControl('', { validators: [Validators.required] }),
}, { validators: passwordMatchValidator() });

Displaying validation errors in the template

<div *ngIf="registrationForm.errors?.passwordMismatch && registrationForm.touched">
  Passwords do not match.
</div>

UX Best Practices for Forms

  • Immediate validation feedback: Show error messages as soon as users interact with the field (touched or dirty state).
  • Disable submit button: Prevent form submission if the form is invalid.
  • Use ARIA roles and labels: Improve accessibility by associating error messages and inputs properly.
  • Clear error messages: Keep validation messages simple and actionable.

Summary

  • Typed reactive forms improve type safety and developer experience.
  • Custom validators enable complex validation logic like password confirmation.
  • User experience is critical: timely validation messages and form control states prevent user frustration.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

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