Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Form validation

Validation stops a form from being submitted with empty or wrong data and tells the user what to fix. In reactive forms the rules are set in the class together with the fields, and the template shows the messages.

Built-in validators

Rules go into the validators array in the second argument of FormControl:

// src/app/post-form/post-form.ts
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
 
@Component({
  selector: 'app-post-form',
  imports: [ReactiveFormsModule],
  templateUrl: './post-form.html',
})
export class PostForm {
  form = new FormGroup({
    title: new FormControl('', {
      nonNullable: true,
      validators: [Validators.required, Validators.minLength(5)],
    }),
    body: new FormControl('', {
      nonNullable: true,
      validators: [Validators.required],
    }),
  });
}

The title is required and at least five characters long, the text is required.

ValidatorRule
Validators.requiredThe field is not empty
Validators.minLength(5)At least 5 characters
Validators.maxLength(100)At most 100 characters
Validators.emailLooks like an email address
Validators.min(1), Validators.max(10)A number within limits
Validators.pattern(/^[a-z]+$/)Matches a regular expression

Error messages

Each field has state properties. invalid means a rule is broken, touched means the user has already visited the field. The hasError() method checks a specific rule:

<!-- src/app/post-form/post-form.html -->
<form [formGroup]="form" (ngSubmit)="save()">
  <input formControlName="title" placeholder="Title" />
 
  @let title = form.controls.title;
  @if (title.touched && title.hasError('required')) {
    <p class="error">Enter a title.</p>
  }
  @if (title.touched && title.hasError('minlength')) {
    <p class="error">At least 5 characters.</p>
  }
 
  <textarea formControlName="body" placeholder="Text"></textarea>
  <button type="submit" [disabled]="form.invalid">Publish</button>
</form>

The @let block declares a short name right in the template. Click into the title field and leave it — "Enter a title." appears. Type "Test" — the message changes to "At least 5 characters.". The button stays disabled while the form is invalid.

State CSS classes

Angular adds the classes ng-valid, ng-invalid, ng-touched and ng-dirty to fields by itself. A red border on an invalid field needs no code:

/* src/app/post-form/post-form.css */
input.ng-invalid.ng-touched,
textarea.ng-invalid.ng-touched {
  border: 1px solid crimson;
}

Checking on submit

If you do not disable the button, check the form in the method and highlight all errors at once:

// src/app/post-form/post-form.ts
save() {
  if (this.form.invalid) {
    this.form.markAllAsTouched();
    return;
  }
  console.log('Sending', this.form.getRawValue());
}

markAllAsTouched() marks all fields as visited, and messages appear next to every invalid field.

A custom validator

A validator is a function that receives a field and returns null if everything is fine, or an object describing the error:

// src/app/no-spam.ts
import { AbstractControl, ValidationErrors } from '@angular/forms';
 
export function noSpam(control: AbstractControl): ValidationErrors | null {
  const text = String(control.value).toLowerCase();
  return text.includes('casino') ? { spam: true } : null;
}

Use it like a built-in one: validators: [Validators.required, noSpam], and check hasError('spam') in the template.

Common mistake

Showing errors as soon as the form opens. An empty required field is invalid from the start, and the user sees red text before doing anything. Add touched or dirty to the condition, as in the example above.

Thanks for reading!

Donate to the site — we work hard for you.