Open In App

How to perform custom validation in Angular forms?

Last Updated : 01 May, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

Angular’s built-in form controls provide a set of predefined validators for common validation such as required fields, email format, and minimum/maximum values. However, there may be cases where you need to implement custom validation logic specific to your application’s requirements.

Angular offers several approaches to accomplish this, allowing you to create custom validators and apply them to form controls.

Steps to perform custom validation

Step 1: Create a new Angular application

ng my-app

Step 2: Once the application is created, navigate to the project directory

cd my-app

Folder Structure:

Screenshot-2024-04-18-231615

folder structure

Dependencies:

"dependencies": {
"@angular/animations": "^17.3.0",
"@angular/common": "^17.3.0",
"@angular/compiler": "^17.3.0",
"@angular/core": "^17.3.0",
"@angular/forms": "^17.3.0",
"@angular/platform-browser": "^17.3.0",
"@angular/platform-browser-dynamic": "^17.3.0",
"@angular/platform-server": "^17.3.0",
"@angular/router": "^17.3.0",
"@angular/ssr": "^17.3.3",
"express": "^4.18.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.3"
}

Custom Validator Functions

In this approach, you’ll create a standalone validator function that encapsulates your custom validation logic. Here’s an example of a validator function that checks if a password meets specific complexity requirements:

Example:

JavaScript
//app.component.ts

import { CommonModule, JsonPipe, NgIf } from '@angular/common';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { Component, inject } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { SharedModule } from './shared/shared.module';
import { AbstractControl, FormControl, FormGroup, FormsModule, 
    ReactiveFormsModule, ValidationErrors, 
    ValidatorFn, Validators } from '@angular/forms';

@Component({
    selector: 'app-root',
    standalone: true,
    imports: [RouterOutlet, NgIf, JsonPipe, SharedModule,
         ReactiveFormsModule, FormsModule],
    templateUrl: `
    <div>
      <h2>Password Complexity Validator</h2>
      <form [formGroup]="form">
        <div>
          <label for="password">Password:</label>
          <input type="password" id="password" formControlName="password">
          <div *ngIf="form.get('password')?.hasError('required')">
            Password is required.
          </div>
          <div *ngIf="form.get('password')?.hasError('passwordComplexity')">
            Password must contain at least one uppercase letter, 
            one lowercase letter, and one number.
          </div>
        </div>
        <button type="submit" [disabled]="form.invalid">Submit</button>
      </form>
    </div>
  `,
    styleUrl: './app.component.css'
})
export class AppComponent {
    form = new FormGroup({
        password: new FormControl('', [Validators.required,
             this.passwordComplexityValidator()])
    });

    passwordComplexityValidator(): ValidatorFn {
        return (control: AbstractControl): ValidationErrors | null => {
            const password = control.value;

            const hasUppercase = /[A-Z]+/.test(password);
            const hasLowercase = /[a-z]+/.test(password);
            const hasNumeric = /[0-9]+/.test(password);
            const passwordValid = hasUppercase && hasLowercase && hasNumeric;

            return !passwordValid ? { passwordComplexity: true } : null;
        };
    }
}

Custom Directive with a Validator

In this approach, you’ll create a custom directive that implements the Validator interface. This approach is useful when you want to encapsulate the validation logic within a reusable directive that can be applied to multiple form controls.

Generate a new directive using the Angular CLI:

ng generate directive password-complexity

Updated Folder Structure:

Screenshot-2024-04-18-233425

folder structure

Example: This example implements the Validator interface in the directive with custom validation logic.

JavaScript
//app.component.ts

import { CommonModule, JsonPipe, NgIf } from '@angular/common';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { Component, inject } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { SharedModule } from './shared/shared.module';
import {
    AbstractControl, FormControl, FormGroup, FormsModule,
    ReactiveFormsModule, ValidationErrors, ValidatorFn,
    Validators
} from '@angular/forms';
import { PasswordComplexityDirective } from './password-complexity.directive';

@Component({
    selector: 'app-root',
    standalone: true,
    imports: [RouterOutlet, NgIf, JsonPipe, SharedModule,
        ReactiveFormsModule, FormsModule, PasswordComplexityDirective],
    template: `
   <div>
   <h2>Password Complexity Validator</h2>
   <form [formGroup]="form">
     <div>
       <label for="password">Password:</label>
       <input type="password" id="password" 
       formControlName="password" appPasswordComplexity>
       <div *ngIf="form.get('password')?.invalid && (form.get('password')?
       .dirty || form.get('password')?.touched)">
         <div *ngIf="form.get('password')?.hasError('required')">
           Password is required.
         </div>
         <div *ngIf="form.get('password')?.hasError('passwordComplexity')">
           Password must contain at least one uppercase letter, 
           one lowercase letter, and one number.
         </div>
       </div>
     </div>
     <button type="submit" [disabled]="form.invalid">Submit</button>
   </form>
 </div>
    `,
    styleUrl: './app.component.css'
})
export class AppComponent {
    form = new FormGroup({
        password: new FormControl('', [
            Validators.required
        ])
    });

}
JavaScript
//password-complexity.directive.ts

import { Directive } from '@angular/core';
import {
    AbstractControl, NG_VALIDATORS,
    ValidationErrors, Validator
} from '@angular/forms';

@Directive({
    selector: '[appPasswordComplexity]',
    standalone: true,
    providers: [
        {
            provide: NG_VALIDATORS,
            useExisting: PasswordComplexityDirective,
            multi: true
        }
    ]
})
export class PasswordComplexityDirective implements Validator {
    validate(control: AbstractControl): ValidationErrors | null {
        const password = control.value;

        // Custom validation logic
        const hasUppercase = /[A-Z]+/.test(password);
        const hasLowercase = /[a-z]+/.test(password);
        const hasNumeric = /[0-9]+/.test(password);
        const passwordValid = hasUppercase && hasLowercase && hasNumeric;

        return !passwordValid ? { passwordComplexity: true } : null;
    }
}

Output:
pass_validation



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads