Building a Custom Validator for Multiple FormControl Inputs | Angular

Michael Dacanay
2 min readSep 1, 2024

--

Synonyms: Reactive forms ~ [formControl] directive ~ model-driven form

Demo: Angular FormControl | Custom Validator — StackBlitz

When building a custom validator that validates the state depending on multiple inputs, a FormGroup is required. If there are separate FormControl inputs (textbox or dropdown) for month, day, and year, is this a valid birthdate?

export class DobFormMoleculeComponent {
...
dateForm = new FormGroup(
{
day: new FormControl('', Validators.required),
month: new FormControl('January'),
year: new FormControl('', [
Validators.required,
Validators.minLength(4),
Validators.maxLength(4),
]),
},
{ validators: dateValidator }
);
...
}

Here, there is a validator on an independent FormControl, as well as a dateValidator on the collective dateForm FormGroup, comprised of the day, month, and year form controls.

export const dateValidator: ValidatorFn = (
control: AbstractControl
): ValidationErrors | null => {
const day: number = control.get('day')?.value;
const month: number = monthNumber(control.get('month')?.value);
const year: number = control.get('year')?.value;

// If any of the fields are empty
if (!day || month == -1 || !year) {
return { dateInvalid: true }; // invalid state, type ValidationErrors
}

// Check if the date is valid
const date: Date = new Date(year, month, day);
if (
// force type coercion, + makes sure that JS interpreter treats variable as number, not string
date.getFullYear() === +year &&
date.getMonth() === +month &&
date.getDate() === +day
) {
// valid date, now check if it is today or before
if (date.getTime() <= Date.now()) {
return null; // valid state
} else {
return { dateInvalid: true };
}
} else {
return { dateInvalid: true }; // Invalid date
}
};

function monthNumber(month: string) {
let months: string[] = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];

// returns -1 when argument is invalid
return months.indexOf(month);
}

The dateValidator function definition can be represented like this:

function dateValidator(control: AbstractControl) => { ... }

The parameter is an AbstractControl, and the return type is ValidationErrors | null. Return null when the state is valid.

That function takes an Angular control object and returns either null if the control value is valid or a validation error object. The validation error object typically has a property whose name is the validation key.

In the template:

<form [formGroup]="dateForm">
<app-dropdown-atom
class="month-dropdown"
ngDefaultControl
formControlName="month" // <-- when formControl is inside a formGroup
[options]="months"
required
></app-dropdown-atom>
</form>

Since month form control is nested in a formGroup, use formControlName to bind the FormControl variable in the TS file with the value typed in the input textbox ( app-dropdown-atom ).

Sources:

--

--

Michael Dacanay
Michael Dacanay

Written by Michael Dacanay

Intern at Tesla, Fidelity. Student at North Carolina State University

No responses yet