Angular provides both built-in validators and the ability to create custom validators. Below are details on both, including how to disable browser validation and how to use various built-in validators in Angular.


1. Disabling Browser Validation with novalidate Attribute

To avoid browser interference with Angular’s form validation (especially for custom validators), you need to disable the native HTML5 form validation. This can be done by adding the novalidate attribute to the <form> tag.

Example:

<form #myForm="ngForm" novalidate (ngSubmit)="onSubmit(myForm)">
  <label for="email">Email:</label>
  <input type="email" id="email" name="email" [(ngModel)]="email" required email />
  <button type="submit" [disabled]="!myForm.valid">Submit</button>
</form>

Explanation:

  • Adding novalidate ensures that the browser’s native validation won’t run, and Angular will take control of validation.
  • Here, required and email are built-in validators from Angular.

2. Built-in Validators

Angular comes with a set of built-in validators that can be used directly in the template or within the reactive form setup. These validators are based on HTML5 validation attributes.

a. required Validator:

Makes the field mandatory.

<input type="text" [(ngModel)]="name" name="name" required />

b. minlength Validator:

Ensures the input meets the minimum length requirement.

<input type="text" [(ngModel)]="password" name="password" minlength="6" required />

c. maxlength Validator:

Ensures the input does not exceed a specified length.

<input type="text" [(ngModel)]="username" name="username" maxlength="15" required />

d. pattern Validator:

Validates input against a regular expression.

<input type="text" [(ngModel)]="zipcode" name="zipcode" pattern="^[0-9]{5}$" required />

3. Commonly Used Form Properties

  • touched: A field becomes “touched” when the user has visited it (i.e., clicked or focused and then blurred).
  • untouched: A field remains “untouched” until a user interacts with it.
  • valid: The form is valid if all fields pass the validation.
  • invalid: The form is invalid if any field fails validation.
  • dirty: A field is “dirty” if the user has modified it.
  • pristine: A field is “pristine” if the user has not modified it.

These properties can be accessed to give feedback to users, such as showing error messages when the form is invalid or highlighting the input field.

Example (Displaying Validation States):

<form #myForm="ngForm" novalidate (ngSubmit)="onSubmit(myForm)">
  <label for="name">Name:</label>
  <input type="text" id="name" name="name" [(ngModel)]="name" required #nameField="ngModel" />
 
  <div *ngIf="nameField.invalid && nameField.touched">
    <p>Name is required!</p>
  </div>
 
  <button type="submit" [disabled]="!myForm.valid">Submit</button>
</form>

Explanation:

  • If the name input is invalid and the field has been touched by the user, an error message will be displayed.
  • The ngModel directive gives access to the form control’s properties (invalid, touched, etc.) using #nameField="ngModel".

4. Custom Validator Example

A custom validator is a function that can be used to check form values in a more complex way. It can be used to validate a field’s value against specific business logic.

a. Creating a Custom Validator (Email Validator)

email.validator.ts (Custom Validator File):
import { AbstractControl, ValidationErrors } from '@angular/forms';
 
export function emailValidator(control: AbstractControl): ValidationErrors | null {
  const emailPattern = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/;
  if (control.value && !emailPattern.test(control.value)) {
    return { 'invalidEmail': true };
  }
  return null;
}

Explanation:

  • This custom validator checks whether the email input matches a specific pattern (simple email format check).
  • If the value does not match the email pattern, it returns an error object; otherwise, it returns null.
app.component.ts (Using the Custom Validator):
import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';
import { emailValidator } from './email.validator';
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  email: string = '';
 
  onSubmit(form: NgForm): void {
    if (form.valid) {
      console.log('Form Submitted');
    } else {
      console.log('Form invalid');
    }
  }
}
app.component.html (Using the Validator in Template):
<form #myForm="ngForm" novalidate (ngSubmit)="onSubmit(myForm)">
  <label for="email">Email:</label>
  <input type="email" id="email" name="email" [(ngModel)]="email" [ngModelOptions]="{updateOn: 'blur'}" required email
         [ngModel]="email" [ngModelOptions]="{standalone: true}" #emailField="ngModel" />
         
  <div *ngIf="emailField.invalid && emailField.touched">
    <p *ngIf="emailField.errors?.invalidEmail">Invalid email format!</p>
    <p *ngIf="emailField.errors?.required">Email is required!</p>
  </div>
  
  <button type="submit" [disabled]="myForm.invalid">Submit</button>
</form>

Explanation:

  • The custom emailValidator is applied to the email input field.
  • If the email format is invalid or the field is empty, appropriate error messages are displayed.
  • ngModelOptions is used to customize form control behavior.

5. Example of Form Validation States

Here’s how to use built-in validation properties like touched, dirty, valid, etc., for providing dynamic feedback.

<form #myForm="ngForm" novalidate (ngSubmit)="onSubmit(myForm)">
  <label for="username">Username:</label>
  <input type="text" id="username" name="username" [(ngModel)]="username" required #usernameField="ngModel" />
 
  <div *ngIf="usernameField.touched && usernameField.invalid">
    <p *ngIf="usernameField.errors?.required">Username is required!</p>
  </div>
 
  <div *ngIf="usernameField.dirty">
    <p>Field has been modified!</p>
  </div>
 
  <button type="submit" [disabled]="!myForm.valid">Submit</button>
</form>

Explanation:

  • If the username field is touched and invalid, the “Username is required!” message is shown.
  • If the user has modified the field (making it dirty), a message will indicate that the field has been changed.

Conclusion

  • Browser validation: Disable it using the novalidate attribute to avoid conflicts with Angular’s form validation.
  • Built-in validators: Use Angular’s built-in required, minlength, maxlength, and pattern validators.
  • Validation properties: Use touched, untouched, valid, invalid, dirty, and pristine to handle form state.
  • Custom Validators: Create custom validation logic to handle complex validation scenarios.

By using both built-in and custom validators, you can easily manage form validation in Angular and provide a better user experience.