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?
answer
- second constructor argument or options
- a map keyed by error name
- null means valid
- minlength, lowercase, with requiredLength
basics
~20 sAngular'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 sIn 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 linesimport {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 valuesgo deeper
Recall how to pass Validators.required and friends to a FormControl, and read control.errors, hasError() and the lowercase error keys.
Explain how results merge into one map, which validators skip empty values, and why runtime validator changes need updateValueAndValidity().
Show that error payloads drive messages without duplicated constants, and catch pattern-anchoring and global-flag bugs in review.
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.