The modern web is driven by data, but applications crash when data is incorrect. Understanding Form Validation in Angular guarantees that your application functions properly and securely, regardless of whether you create a straightforward login screen or a complicated checkout procedure. Imagine a digital form similar to the one used to apply for a passport at a government office. The clerk will reject it right away if you forget your photo or write your birthday incorrectly. Rather than waiting weeks for a rejection letter, you make the necessary corrections there.
Angular serves as that productive clerk. Before sending user input to the server, it verifies it in real time. We will examine an Angular 21 reactive forms validation tutorial in this guide, contrast it with template-driven methods, and make sure your app manages data expertly.
Angular 21 Form Validation
Angular provides two completely different ways to deal with user input: Template-Driven Forms and Reactive Forms. Which one to use depends on your requirements and the complexity of your application.
Template-Driven forms are very directive-heavy in your HTML template. They are great for simple use cases, such as a newsletter subscription form. You have to write less code in your component file, and most of the magic happens behind the scenes in Angular.
On the other hand, when it comes to complex use cases, such as implementing form validation in angular, developers are often steered towards Reactive Forms. This method gives you direct access to the form object model. It is more robust, scalable, and easier to test. If you are dealing with dynamic fields or complex cross-field validation, Reactive Forms are the way to go.
Template-Driven Forms Validation Example
Let’s look at a simple angular 21 template driven forms validation example. Here, we bind data directly to the template using the ngModel directive. We add validation attributes like required and minlength directly to the HTML input tags.
The framework tracks the state of these controls automatically. It knows if the user touched the field or if the value changed.
login.component.ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-login',
standalone: true,
imports: [FormsModule, CommonModule],
templateUrl: './login.component.html',
styleUrl: './login.component.css'
})
export class LoginComponent {
model = {
email: '',
password: ''
};
onSubmit(form: any) {
if (form.valid) {
console.log('Form Submitted', this.model);
}
}
}
login.component.html
<div class="container">
<h2>Login</h2>
<form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm)" novalidate>
<div class="form-group">
<label for="email">Email Address</label>
<input
type="email"
id="email"
name="email"
class="form-control"
[(ngModel)]="model.email"
required
email
#email="ngModel">
<div *ngIf="email.invalid && (email.dirty || email.touched)" class="error-message">
<div *ngIf="email.errors?.['required']">
Email is required.
</div>
<div *ngIf="email.errors?.['email']">
Please enter a valid email address.
</div>
</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input
type="password"
id="password"
name="password"
class="form-control"
[(ngModel)]="model.password"
required
minlength="6"
#password="ngModel">
<div *ngIf="password.invalid && (password.dirty || password.touched)" class="error-message">
<div *ngIf="password.errors?.['required']">
Password is required.
</div>
<div *ngIf="password.errors?.['minlength']">
Password must be at least 6 characters long.
</div>
</div>
</div>
<button type="submit" [disabled]="loginForm.invalid">
Sign In
</button>
</form>
</div>
Reactive Forms Validation in Angular
For enterprise-level applications, developers prefer the Reactive approach. This method moves the logic from the HTML to the TypeScript class. It gives you explicit control over the form state. We use FormBuilder to define the structure and attach validators. This makes the code cleaner and the logic easier to follow.
register.component.ts
import { Component, inject } from '@angular/core';
import { FormBuilder, Validators, ReactiveFormsModule, AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
import { CommonModule } from '@angular/common';
export function restrictedNameValidator(restrictedName: RegExp): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const forbidden = restrictedName.test(control.value);
return forbidden ? { restrictedName: { value: control.value } } : null;
};
}
@Component({
selector: 'app-register',
standalone: true,
imports: [ReactiveFormsModule, CommonModule],
templateUrl: './register.component.html',
styleUrl: './register.component.css'
})
export class RegisterComponent {
private fb = inject(FormBuilder);
registerForm = this.fb.group({
username: ['', [
Validators.required,
Validators.minLength(4),
restrictedNameValidator(/admin/i)
]],
email: ['', [
Validators.required,
Validators.email
]],
password: ['', [
Validators.required,
Validators.pattern('^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$')
]]
});
get username() { return this.registerForm.get('username'); }
get email() { return this.registerForm.get('email'); }
get password() { return this.registerForm.get('password'); }
onSubmit() {
if (this.registerForm.valid) {
console.log('Registration Data:', this.registerForm.value);
this.registerForm.reset();
} else {
this.registerForm.markAllAsTouched();
}
}
}
register.component.html
<div class="container">
<h2>Create Account</h2>
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="username">Username</label>
<input
id="username"
type="text"
formControlName="username"
class="form-control">
<div *ngIf="username?.invalid && (username?.dirty || username?.touched)" class="error-message">
<div *ngIf="username?.errors?.['required']">
Username is required.
</div>
<div *ngIf="username?.errors?.['minlength']">
Username must be at least 4 characters.
</div>
<div *ngIf="username?.errors?.['restrictedName']">
You cannot use the word "admin" in your username.
</div>
</div>
</div>
<div class="form-group">
<label for="reg-email">Email</label>
<input
id="reg-email"
type="email"
formControlName="email"
class="form-control">
<div *ngIf="email?.invalid && (email?.dirty || email?.touched)" class="error-message">
<div *ngIf="email?.errors?.['required']">
Email is required.
</div>
<div *ngIf="email?.errors?.['email']">
Please enter a valid email address.
</div>
</div>
</div>
<div class="form-group">
<label for="reg-password">Password</label>
<input
id="reg-password"
type="password"
formControlName="password"
class="form-control">
<div *ngIf="password?.invalid && (password?.dirty || password?.touched)" class="error-message">
<div *ngIf="password?.errors?.['required']">
Password is required.
</div>
<div *ngIf="password?.errors?.['pattern']">
Password must be 8+ chars with at least one letter and one number.
</div>
</div>
</div>
<button type="submit" [disabled]="registerForm.invalid">
Register
</button>
</form>
</div>
Creating Custom Validators
Sometimes, built-in checks like required or email fall short. You might need to verify if a username contains specific restricted words or if a coupon code follows a strict format. This is where creating custom validators in angular becomes essential.
Let’s take another example with creating custom validators. It receives a control and returns null if the value is valid, or an error object if it fails.
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
export function restrictedWordsValidator(words: string[]): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (!control.value) {
return null;
}
const forbidden = words.find(word => control.value.includes(word));
return forbidden ? { restrictedWord: { value: forbidden } } : null;
};
}
You can then add this validator to your form definition easily. This flexibility ensures your angular 21 form validation error messages are precise and helpful to the user.
Conclusion
Efficient validation enhances user experience while safeguarding your database. We looked at the strength of the reactive approach and the ease of use of template-driven forms.
Don’t forget to keep to the best practices for Angular 21 input validation, which include giving clear feedback, validating on the server side as well, and maintaining testable validation logic. Angular gives you the tools you need to create secure and trustworthy forms, irrespective of whether you use the template or the reactive path.
