Modern Angular Form validation
recently I tweaks on the Angular form validation and come up with a handy solution to simplify the tedious validation process on the form. A small library is created for this article.
The basic process to define a form in the Angular project.
1. Define your form in your component
export type ValidationMessages = { [key: string]: { [key: string]: string } };
@Component({
selector: 'signup',
templateUrl: './signup.page.html',
styleUrls: ['./signup.page.scss'],
providers: [FormValidationService], <-- we rely on this service to do the form validation.
})
export class SignupPageComponent {
...
private destroyRef = inject(DestroyRef);
private emailCheckValidator = inject(emailAvailableValidator); // this is a customized async validator
// all the input elements in the form (signal var)
formInputElements = viewChildren(FormControlName, {
read: ElementRef,
});
// the generated form validation error message will be stoe here
displayMessage = computed(() => this.formValidationService.displayMessage());
registerForm = this.fb.group({
email: [
'',
{
validators: [
Validators.required,
Validators.pattern(regexConfig.emailRegex),
],
asyncValidators: [
this.emailCheckValidator.validate.bind(this.emailCheckValidator),
],
updateOn: 'blur',
},
],
password: ['', [Validators.required, Validators.minLength(6)]],
fullname: ['', Validators.required],
});
get email(): AbstractControl {
return this.registerForm.get('email') as AbstractControl;
}
get password(): AbstractControl {
return this.registerForm.get('password') as AbstractControl;
}
get fullname(): AbstractControl {
return this.registerForm.get('fullname') as AbstractControl;
}
2. create the validation related messages for each formControl
private validationMessages: ValidationMessages = {
email: {
required: 'validate.email_required',
pattern: 'validate.email_valid',
email_taken: 'validate.email_is_taken',
},
password: {
required: 'validate.password_required',
minlength: 'validate.password_minlength',
},
fullname: {
required: 'validate.fullname_required',
},
};
3. register the validator for the form in constructor
constructor() {
this.formValidationService
.registerValidator(
this.validationMessages,
this.formInputElements(),
this.registerForm,
)
.subscribe();
}
4 if we want to set the server side valdation message to the form validation, config in the catchError
this.auth
.emailSignup(this.registerForm.value as UserSignupForm)
.pipe(
takeUntilDestroyed(this.destroyRef),
catchError((res) =>
throwError(() => {
this.formValidationService.setServerValidationErrors(
this.registerForm,
res,
);
return res;
}),
),
)
.subscribe();
5. now config the template
to render the error message in the template, we use a component to do that automatically
<form [formGroup]="registerForm">
<div>
<memodir-error-message
[control]="email"
[validationMessages]="displayMessage()"
/>
<input type="text" formControlName="email" />
</div>
<div>
<memodir-error-message
[control]="password"
[validationMessages]="displayMessage()"
/>
<input type="password" formControlName="password" />
</div>
<div>
<memodir-error-message
[control]="fullname"
[validationMessages]="displayMessage()"
/>
<input type="text" formControlName="fullname" />
</div>
<button (click)="signUp()" [disabled]="registerForm.invalid">
Signup
</button>
</form>