$validators / $asyncValidators in modern Angular
Validation directives that wrote to $validators and $asyncValidators become plain functions attached to the form control.
Before · AngularJS
app.directive('companyEmail', function ($q, UserService) {
return {
require: 'ngModel',
link: function (scope, el, attrs, ngModel) {
ngModel.$validators.company = function (v) { return !v || v.endsWith('@acme.com'); };
ngModel.$asyncValidators.unique = function (value) {
return UserService.exists(value).then(function (taken) {
if (taken) { return $q.reject(); }
});
};
}
};
});After · Angularexport const companyEmail: ValidatorFn = control =>
!control.value || control.value.endsWith('@acme.com') ? null : { company: true };
export function uniqueEmail(): AsyncValidatorFn {
const users = inject(UserService);
return control => users.exists(control.value).pipe(
map(taken => (taken ? { unique: true } : null)),
);
}
// component field, so inject() has an injection context
email = new FormControl('', {
nonNullable: true,
validators: [Validators.required, companyEmail],
asyncValidators: [uniqueEmail()],
});What changes
- A $validators function returned true or false; a ValidatorFn returns null when valid or an error object like { company: true }.
- An $asyncValidators promise that rejected becomes an AsyncValidatorFn that emits null or an error object.
- No directive is needed with reactive forms. Async validators run only after the sync ones pass, as before.
Migration tip. An async validator runs on every value change. Set updateOn: 'blur' on the control or add a delay inside the validator, the job ng-model-options debounce did in AngularJS.
Related
Planning a migration or a micro-frontend platform?
In a 20-minute call you describe your situation and I tell you how I'd approach it. If it isn't worth doing yet, I'll say so.
Message me on LinkedIn