skip to content

Validator Functions

Forms run ValidatorFn and AsyncValidatorFn functions that fill a control's errors map and set it VALID, INVALID or PENDING. Interviewers ask for custom, async and cross-field validators.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In Angular reactive forms, how do you attach built-in validators such as Validators.required and Validators.minLength, and how do you read the errors they produce?

level: juniorimportance: must knowfreq 72%

answer

  1. second constructor argument or options
  2. a map keyed by error name
  3. null means valid
  4. minlength, lowercase, with requiredLength

basics

~20 s

Angular's built-in validators are functions passed to a control, either as the second argument or in the validators option. Each failing validator adds a key to control.errors, which is null when the control is valid.

solid answer

~40 s

In reactive forms you pass validators when you create the control: `new FormControl('', [Validators.required, Validators.minLength(8)])`, or `{validators: [...]}` in the options object. Angular runs them on each value change and merges their results into `control.errors`, which is `null` when all pass and otherwise an object keyed by error name: `required: true`, `minlength: {requiredLength, actualLength}`, `email: true`, `min: {min, actual}`, `pattern: {requiredPattern, actualValue}`. The control's `status` becomes `INVALID`, and `hasError('minlength')` or `getError('minlength')` read a single entry. Note the lowercase keys, and that `email`, `pattern` and `minLength` let an empty value through: only `required` (or `requiredTrue` for a checkbox) rejects empty input, so an optional field stays valid until the user types something.

code

ts · 10 lines
ts
import {FormControl, Validators} from '@angular/forms';

const password = new FormControl('abc', [Validators.required, Validators.minLength(8)]);

console.log(password.status);                 // 'INVALID'
console.log(password.errors);                 // {minlength: {requiredLength: 8, actualLength: 3}}
console.log(password.hasError('minlength'));  // true

password.setValue('');
console.log(password.errors);                 // {required: true} - minLength skips empty values

go deeper

for a junior

Recall how to pass Validators.required and friends to a FormControl, and read control.errors, hasError() and the lowercase error keys.

for a middle

Explain how results merge into one map, which validators skip empty values, and why runtime validator changes need updateValueAndValidity().

for a senior

Show that error payloads drive messages without duplicated constants, and catch pattern-anchoring and global-flag bugs in review.

for a principal

Decide how error keys map to user-facing copy across the app, so validators and messages stay consistent without a lookup table per form.

## What a validator is in Angular In Angular's `@angular/forms`, a **validator** is a plain function with the `ValidatorFn` shape: it receives an `AbstractControl` and returns either `null` (valid) or a `ValidationErrors` object (invalid). The `Validators` class exposes the built-in ones as static members. Some are validators already (`Validators.required`, `Validators.email`); others are **factories** that take a parameter and return a validator (`Validators.minLength(8)`, `Validators.min(18)`, `Validators.pattern(...)`). ## Attaching them in reactive forms You pass validators when you construct the control. Two forms are equivalent: ```ts import {FormControl, Validators} from '@angular/forms'; const password = new FormControl('', [Validators.required, Validators.minLength(8)]); const email = new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.email], }); ``` With `FormBuilder`, the array form `fb.control('', [Validators.required])` or a tuple inside `fb.group({password: ['', [Validators.required]]})` does the same thing. Validators can also be changed later with `addValidators()`, `removeValidators()` and `setValidators()`, followed by `updateValueAndValidity()` so the new set takes effect. ## The errors map After every value change Angular runs all sync validators and **merges** their results into one object on `control.errors`: | Validator | Error key | Payload | |---|---|---| | `Validators.required` | `required` | `true` | | `Validators.requiredTrue` | `required` | `true` | | `Validators.email` | `email` | `true` | | `Validators.minLength(n)` | `minlength` | `{requiredLength, actualLength}` | | `Validators.maxLength(n)` | `maxlength` | `{requiredLength, actualLength}` | | `Validators.min(n)` / `max(n)` | `min` / `max` | `{min, actual}` / `{max, actual}` | | `Validators.pattern(p)` | `pattern` | `{requiredPattern, actualValue}` | The keys for the length validators are **lowercase** (`minlength`), even though the factory is `minLength`. The payloads let a message say "at least 8 characters, you have 5" without hard-coding the number twice. Useful accessors: - `control.errors`: the whole map, or `null`. - `control.hasError('minlength')`: a boolean for one key. - `control.getError('minlength')`: that key's payload. - On a group, both take a path: `form.hasError('required', 'password')`. - `control.status`: `VALID`, `INVALID`, `PENDING` or `DISABLED`; `valid` and `invalid` are shortcuts. ## Changing validators later Validators are stored by **function reference**, which has practical consequences: - `setValidators([...])` replaces the whole list; `addValidators()` and `removeValidators()` change it without touching the rest. - `removeValidators()` and `hasValidator()` compare references, so `Validators.required` works (it is one shared function) but `Validators.minLength(8)` does not match a second `Validators.minLength(8)` call, because each factory call returns a new function. Keep the instance in a variable if you plan to remove it. - None of these re-run validation. Call `updateValueAndValidity()` afterwards. ## Empty values pass most validators A frequent surprise: `Validators.email`, `Validators.pattern`, `Validators.minLength`, `Validators.min` and `Validators.max` all **skip empty values** so that optional fields are not flagged before the user types. Only `required` (and `requiredTrue` for checkboxes) rejects emptiness. So a mandatory email field needs both `Validators.required` and `Validators.email`. ## Showing the error In a template the usual pattern reads a specific key and waits until the user has interacted: ```html @if (password.hasError('minlength') && password.touched) { <p>Use at least {{ password.getError('minlength').requiredLength }} characters.</p> } ``` Angular also adds CSS classes such as `ng-invalid` and `ng-touched` to bound elements, which a stylesheet can use for the red border. ## Pitfalls 1. Checking `errors.minLength` (camel case) and never matching, because the key is `minlength`. 2. Using `Validators.min` on a text length, or `minLength` on a number: `min` compares a numeric value, `minLength` counts characters or items. 3. Passing a `RegExp` with the `g` or `y` flag to `Validators.pattern`. `RegExp.prototype.test` then keeps `lastIndex` between runs, so the same input can alternate between valid and invalid. 4. Forgetting that a **string** pattern is anchored for you (`^` and `$` are added if missing), while a `RegExp` is used as written. 5. Adding validators at runtime and forgetting `updateValueAndValidity()`, so the displayed status is stale until the next keystroke. Knowing the error keys and the empty-value rule is what separates a candidate who has built forms from one who has only read about them.

  • How does Validators.pattern treat a string pattern differently from a RegExp?
    A string is anchored for you: Angular adds `^` and `$` if they are missing, so `'[0-9]{4}'` must match the whole value. A `RegExp` is used exactly as written, so `/[0-9]{4}/` also matches `'ab1234cd'`. Avoid the `g` and `y` flags on a `RegExp`, because `test()` keeps `lastIndex` between runs.
  • You add Validators.required to a control at runtime, but the form still shows it as valid. Why?
    `addValidators()` and `setValidators()` only replace the validator list; they do not re-run it. Call `updateValueAndValidity()` on the control afterwards, and the new validator runs and updates `errors` and `status` straight away.

saying these in an interview costs you the question

  • Validators.email rejects an empty field, so required is unnecessary.
  • The minLength error is stored under the key minLength.
  • Validators.min checks the number of characters in a string.
  • control.errors is an empty object when the control is valid.
  • A RegExp passed to Validators.pattern is anchored automatically.
open as a page

In Angular reactive forms, how do you implement a validator that checks the password and confirm-password fields match, and where does its error appear?

level: middleimportance: must knowfreq 66%

basics

~20 s

Write a ValidatorFn that reads both child values and returns an error such as {passwordMismatch: true}, and attach it to the FormGroup through the validators option. The error lands on the group's errors, not on either control.

open as a page

In Angular reactive forms, how do you write an AsyncValidatorFn that checks whether a username is taken, and what does the PENDING status mean?

level: middleimportance: should knowfreq 55%

basics

~10 s

An AsyncValidatorFn takes the control and returns an Observable or Promise of ValidationErrors or null. Angular runs it only when the sync validators pass, and the control's status is PENDING until the result arrives.

open as a page

An Angular sign-up form's username-availability validator sends a request on every keystroke and sometimes stays PENDING forever; how do you diagnose and fix it?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Async validators run on every value change by default. Set updateOn: 'blur' on the control, or debounce inside the validator with timer() because Angular cancels the previous check, and make sure every path emits a result and completes.

open as a page