Introduction

Angular’s @angular/forms package comes with a Validators class that supports useful built-in validators like required, minLength, maxLength, and pattern. However, there may be form fields that require more complex or custom rules for validation. In those situations, you can use a custom validator.
When using Reactive Forms in Angular, you define custom validators with functions. If the validator does not need to be reused, it can exist as a function in a component file directly. Otherwise, if the validator needs to be reused in other components, it can exist in a separate file.
In this tutorial, you will construct a reactive form with a reusable custom validator to check if a URL meets certain conditions.

Prerequisites

To complete this tutorial, you will need:

Node.js installed locally, which you can do by following How to Install Node.js and Create a Local Development Environment.
Some familiarity with setting up an Angular project.

This tutorial was verified with Node v15.2.1, npm v6.14.8, @angular/core v11.0.0, and @angular/forms v11.0.0.

Step 1 – Setting Up the Project

For the purpose of this tutorial, you will build from a default Angular project generated with @angular/cli.

npx @angular/cli new angular-reactive-forms-custom-validtor-example --style=css --routing=false --skip-tests

This will configure a new Angular project with styles set to “CSS” (as opposed to “Sass”, Less”, or “Stylus”), no routing, and skipping tests.
Navigate to the newly created project directory:

cd angular-reactive-forms-custom-validator-example

To work with reactive forms, you will be using the ReactiveFormsModule instead of the FormsModule.
Open app.module.ts in your code editor amd add ReactiveFormsModule:
src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ReactiveFormsModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

At this point, you should have a new Angular project with ReactiveFormsModule.

Step 2 – Building a Custom Validator

The example custom validator for this tutorial will take a URL string and ensure that it starts with the https protocol and ends with the .io top-level domain.
First, in your terminal, create a shared directory:

mkdir src/shared

Then, in this new directory, create a new url.validator.ts file. Open this file in your code editor and add the following lines of code:
src/shared/url.validator.ts

import { AbstractControl } from '@angular/forms';

export function ValidateUrl(control: AbstractControl) {
  if (!control.value.startsWith('https') || !control.value.includes('.io')) {
    return { invalidUrl: true };
  }
  return null;
}

This code uses the Notice AbstractControl class, which is the base class for FormControls, FormGroups, and FormArrays. This allows access to the value of the FormControl.
This code will check to see if the value startsWith the string of characters for https. It will also check to see if the value includes the string of characters for .io.
If the validation fails, it will return an object with a key for the error name, invalidUrl, and a value of true.
Otherwise, if the validation passes, it will return null.
At this point, your custom validator is ready for use.

Step 3 – Using the Custom Validator

Next, create a form that takes a userName and a websiteUrl.
Open app.component.ts and replace the content with the following lines of code:
src/app/app.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

import { ValidateUrl } from '../shared/url.validator';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  myForm: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.myForm = this.fb.group({
      userName: ['', Validators.required],
      websiteUrl: ['', [Validators.required, ValidateUrl]]
    });
  }

  saveForm(form: FormGroup) {
    console.log('Valid?', form.valid); // true or false
    console.log('Username', form.value.userName);
    console.log('Website URL', form.value.websiteUrl);
  }
}

In this code, the websiteUrl form control uses both the built-in Validators.required and the custom ValidateUrl validator.

Step 4 – Accessing the Errors in the Template

Users interacting with your form will need feedback on what values are failing validation. In the component template, you can use the key you defined in the return value of a custom validator.
Open app.component.html and replace the content with the following lines of code:
src/app/app.component.html

<form [formGroup]="myForm" ngSubmit)="saveForm(myForm)">
  <div>
    <label>
      Username:
      <input formControlName="userName" placeholder="Your username">
    </label>
    <div *ngIf="(
                 myForm.get('userName').dirty ||
                 myForm.get('userName').touched
                ) &&
                myForm.get('userName').invalid"
    >
      Please provide your username.
    </div>
  </div>
  <div>
    <label>
      Website URL:
      <input formControlName="websiteUrl" placeholder="Your website">
    </label>
    <div
      *ngIf="(
              myForm.get('websiteUrl').dirty ||
              myForm.get('websiteUrl').touched
             ) &&
             myForm.get('websiteUrl').invalid"
      >
      Only URLs served over HTTPS and from the .io top-level domain are accepted.
    </div>
  </div>
</form>

At this point, you can compile your application:

npm start

And open it in your web browser. You can interact with the fields for userName and websiteUrl. Ensure that your custom validator for ValidateUrl works as expected with a value that should satisfy the conditions of https and .io: https://example.io.

Conclusion

In this article, you created a reusable custom validator for a reactive form in an Angular application.
For examples of custom validators in template-driven forms and reactive forms, consult Custom Form Validation in Angular.
If you’d like to learn more about Angular, check out our Angular topic page for exercises and programming projects.