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.
To complete this tutorial, you will need:
This tutorial was verified with Node v15.2.1, npm
v6.14.8, @angular/core
v11.0.0, and @angular/forms
v11.0.0.
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
:
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
.
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:
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 FormControl
s, FormGroup
s, and FormArray
s. 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.
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:
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.
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:
<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
.
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.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Great tutorial! Thanks for sharing.
But what if I need to pass extra params to the Validator?