Angular Form Validation Built in Custom is an important Angular topic because it appears in real projects, debugging sessions, and interviews. Learn the meaning first, then connect it to a small working example so the rule does not stay abstract.
For this page, focus on what problem Angular Form Validation Built in Custom solves, where developers usually make mistakes, and how to verify the result. The audit note for this lesson was: limited checklist/practice/mistake/FAQ notes .
A strong understanding of Angular Form Validation Built in Custom should include syntax, behavior, one realistic use case, one failure case, and one quick way to check your work with tools or output.
Angular Form Validation Built in Custom should be studied as a practical Angular lesson, not as a label. Start by naming the input, the rule that changes the input, and the result a learner should be able to predict after reading the page.
In the angular > form-validation page, the notes should connect the definition with a working scenario, a mistake that beginners actually make, and the exact check that proves the fix. That makes the topic useful for coding, debugging, and interview revision.
Angular provides a powerful validation system for both Reactive and Template-driven forms. Validators can be built-in (required, minLength, email) or custom. Angular tracks the validity state of each control and the form as a whole, making it easy to show error messages and disable submit buttons.
| Built-in Validator | Description | Usage |
|---|---|---|
| Validators.required | Field must not be empty | required attribute or Validators.required |
| Validators.minLength(n) | Minimum character count | minlength="3" or Validators.minLength(3) |
| Validators.maxLength(n) | Maximum character count | maxlength="50" or Validators.maxLength(50) |
| Validators.email | Valid email format | email attribute or Validators.email |
| Validators.pattern(regex) | Must match regex pattern | Validators.pattern(/^[0-9]+$/) |
| Validators.min(n) | Minimum numeric value | Validators.min(0) |
| Validators.max(n) | Maximum numeric value | Validators.max(100) |
In reactive forms, validators are added programmatically in the component class. This gives full control over validation logic and makes it easy to test.
import { Component, inject } from '@angular/core';
import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-register',
standalone: true,
imports: [ReactiveFormsModule],
templateUrl: './register.component.html'
})
export class RegisterComponent {
private fb = inject(FormBuilder);
form = this.fb.group({
name: ['', [Validators.required, Validators.minLength(2)]],
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(8)]],
age: [null, [Validators.required, Validators.min(18), Validators.max(120)]]
});
// Convenience getters for template access
get name() { return this.form.get('name')!; }
get email() { return this.form.get('email')!; }
get password() { return this.form.get('password')!; }
get age() { return this.form.get('age')!; }
onSubmit() {
if (this.form.valid) {
console.log('Form submitted:', this.form.value);
} else {
this.form.markAllAsTouched();
}
}
}
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div>
<label>Name</label>
<input formControlName="name" />
@if (name.invalid && name.touched) {
@if (name.errors?.['required']) {
<span class="tl-text-danger">Name is required.</span>
}
@if (name.errors?.['minlength']) {
<span class="tl-text-danger">Name must be at least 2 characters.</span>
}
}
</div>
<div>
<label>Email</label>
<input formControlName="email" type="email" />
@if (email.invalid && email.touched) {
@if (email.errors?.['required']) {
<span class="tl-text-danger">Email is required.</span>
}
@if (email.errors?.['email']) {
<span class="tl-text-danger">Enter a valid email address.</span>
}
}
</div>
<button type="submit" [disabled]="form.invalid">Register</button>
</form>
When built-in validators are not enough, you can create custom validators. A validator is a function that takes an AbstractControl and returns either null (valid) or a ValidationErrors object (invalid).
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
// 1. No whitespace validator
export function noWhitespaceValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const hasWhitespace = (control.value || '').includes(' ');
return hasWhitespace ? { whitespace: true } : null;
};
}
// 2. Password strength validator
export function passwordStrengthValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const value = control.value || '';
const hasUpper = /[A-Z]/.test(value);
const hasLower = /[a-z]/.test(value);
const hasNumber = /[0-9]/.test(value);
const hasSpecial = /[!@#$%^&*]/.test(value);
const isStrong = hasUpper && hasLower && hasNumber && hasSpecial;
return isStrong ? null : { weakPassword: { hasUpper, hasLower, hasNumber, hasSpecial } };
};
}
// 3. Cross-field validator - passwords must match
export function passwordMatchValidator(control: AbstractControl): ValidationErrors | null {
const password = control.get('password');
const confirmPassword = control.get('confirmPassword');
if (!password || !confirmPassword) return null;
return password.value === confirmPassword.value ? null : { passwordMismatch: true };
}
import { Component, inject } from '@angular/core';
import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
import { noWhitespaceValidator, passwordStrengthValidator, passwordMatchValidator } from './custom-validators';
@Component({
selector: 'app-signup',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<input formControlName="username" placeholder="Username" />
@if (form.get('username')?.errors?.['whitespace']) {
<span class="tl-text-danger">No spaces allowed.</span>
}
<input formControlName="password" type="password" placeholder="Password" />
@if (form.get('password')?.errors?.['weakPassword']) {
<span class="tl-text-danger">Password needs uppercase, lowercase, number and special char.</span>
}
<input formControlName="confirmPassword" type="password" placeholder="Confirm Password" />
@if (form.errors?.['passwordMismatch']) {
<span class="tl-text-danger">Passwords do not match.</span>
}
<button type="submit" [disabled]="form.invalid">Sign Up</button>
</form>
`
})
export class SignupComponent {
private fb = inject(FormBuilder);
form = this.fb.group({
username: ['', [Validators.required, noWhitespaceValidator()]],
password: ['', [Validators.required, passwordStrengthValidator()]],
confirmPassword: ['', Validators.required]
}, { validators: passwordMatchValidator });
onSubmit() {
if (this.form.valid) console.log(this.form.value);
}
}
Angular tracks the state of each form control. Understanding these states helps you show the right error messages at the right time.
| State | Property | Meaning |
|---|---|---|
| Pristine | control.pristine | User has not changed the value yet |
| Dirty | control.dirty | User has changed the value |
| Untouched | control.untouched | User has not focused and left the field |
| Touched | control.touched | User has focused and left the field |
| Valid | control.valid | All validators pass |
| Invalid | control.invalid | At least one validator fails |
| Pending | control.pending | Async validator is running |
const state = { topic: "Angular Form Validation Built in Custom", ready: true };
if (state.ready) {
console.log(state.topic + ": render or run the normal path");
}
const response = null;
const message = response?.message ?? "Angular Form Validation Built in Custom: show a clear fallback";
console.log(message);
Memorizing Angular Form Validation Built in Custom without the situation where it is useful.
Connect Angular Form Validation Built in Custom to a concrete Angular task.
Testing Angular Form Validation Built in Custom only with the perfect input.
Include empty, missing, duplicate, incompatible, or failed cases when relevant.
Changing code before reading the visible symptom or error message.
Inspect the output, state, configuration, or stack trace connected to Angular Form Validation Built in Custom.
Memorizing Angular Form Validation Built in Custom without the situation where it is useful.
Connect Angular Form Validation Built in Custom to a concrete Angular task.
The common mistake is memorizing syntax without understanding when the behavior changes or fails.
Remember the problem it solves in Angular, then attach the syntax or steps to that problem.
You can predict the result of a small example, explain a failure case, and choose it over a nearby alternative for a clear reason.
They often copy the syntax but skip the state, input, dependency, selector, route, type, or configuration that controls the behavior.
Explore 500+ free tutorials across 20+ languages and frameworks.