
One thing we often need in user forms — especially during signup — is to make sure two fields match. Like confirming a password. Angular gives us built-in validators like required
, minLength
, and pattern
, but when we need to compare fields, we’ve got to go custom.
Here’s how I handle that by creating a reusable validator directive that checks if two fields in a form are equal.
🔧 The Custom Directive: EqualValidator
This directive is flexible and works with both template-driven and reactive forms.
Here’s the code:
import { Directive, forwardRef, Attribute } from '@angular/core';
import { Validator, FormControl, NG_VALIDATORS } from '@angular/forms';
@Directive({
selector: '[validateEqual][formControlName],[validateEqual][formControl],[validateEqual][ngModel]',
providers: [
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => EqualValidator), multi: true },
]
})
export class EqualValidator implements Validator {
constructor(
@Attribute('validateEqual') public validateEqual: string,
@Attribute('reverse') public reverse: string
) {}
private get isReverse(): boolean {
return this.reverse === 'true';
}
validate(control: FormControl): { [key: string]: any } | null {
const currentValue = control.value;
const targetControl = control.root.get(this.validateEqual);
if (!targetControl) return null;
// Normal direction: this control must match the target
if (!this.isReverse && currentValue !== targetControl.value) {
return { validateEqual: false };
}
// Reverse direction: update the other field's validity
if (this.isReverse) {
if (currentValue === targetControl.value) {
if (targetControl.errors?.['validateEqual']) {
delete targetControl.errors['validateEqual'];
if (!Object.keys(targetControl.errors).length) {
targetControl.setErrors(null);
}
}
} else {
targetControl.setErrors({ validateEqual: false });
}
}
return null;
}
}
🤔 What’s Going On Here?
validateEqual="password"
tells the directive which control to compare with.reverse="true"
is used on the second input (e.g., confirm password) so we can re-trigger validation when it changes.- The logic compares values and either adds or clears validation errors.
🧪 How I Use It in a Form
HTML Template:
<form [formGroup]="registerForm">
<input type="password" formControlName="password" placeholder="Password" />
<input
type="password"
formControlName="confirmPassword"
validateEqual="password"
reverse="true"
placeholder="Confirm Password"
/>
<button type="submit" [disabled]="!registerForm.valid">Register</button>
</form>
Component:
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-register',
templateUrl: './register.component.html'
})
export class RegisterComponent {
registerForm: FormGroup;
constructor(private fb: FormBuilder) {
this.registerForm = this.fb.group({
password: ['', [Validators.required, Validators.minLength(6)]],
confirmPassword: ['', Validators.required]
});
}
}
🏁 Final Thoughts
This directive has been super helpful for me when working with forms that require field matching. It’s clean, reusable, and fits into Angular’s validation system perfectly.
Why I like this approach:
- ✅ Works with reactive and template-driven forms.
- ✅ Keeps templates clean — no need for extra conditional checks.
- ✅ Easily reused in any form that needs field comparison (not just passwords!).
If you’re building forms in Angular and want an easy way to ensure two fields match — like passwords, emails, or even security questions — give this custom validator a shot. It works like a charm 💪
Let me know if you’d like a version with error messages or error styling included!