skip to content

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.