Form Validation and User Experience
In this lesson, we are going to focus on improving the user experience of our two forms by improving how we handle validations. We already have some basic validations in place, but we will be aiming to:
- Display clear validation errors to the user at the appropriate time
- Create a custom validator to check that the
password
andconfirmPassword
fields match
Create a Confirm Password Validator
This is something we have already covered conceptually in the module on form validators — so if the exact implementation is a little unclear here it might be worth going back to that module.
Create a new file at
src/app/auth/register/utils/password-matches.ts
and add the following:
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
export const passwordMatchesValidator: ValidatorFn = (
control: AbstractControl
): ValidationErrors | null => {
const password = control.get('password')?.value;
const confirmPassword = control.get('confirmPassword')?.value;
return password && confirmPassword && password === confirmPassword
? null
: { passwordMatch: true };
};
Add the validator to the
RegisterFormComponent
:
registerForm = this.fb.nonNullable.group(
{
email: ['', [Validators.email, Validators.required]],
password: ['', [Validators.minLength(8), Validators.required]],
confirmPassword: ['', [Validators.required]],
},
{
updateOn: 'blur',
validators: [passwordMatchesValidator],
}
);
Add Validator Error Messages
We are also going to use the same concept we discussed in the advanced forms module here for validator error messages. What we want to do is:
- Not immediately show error messages
- Show an error message if a control is invalid and the user has interacted with the control
- Show an error message if a control is invalid and the form has been submitted
- Show an error message for the status state (i.e. if the login/create account operation fails)
Update the template for the
RegisterFormComponent
to reflect the following:
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()" #form="ngForm">
<mat-form-field appearance="fill">
<mat-label>email</mat-label>
<input
matNativeControl
formControlName="email"
type="email"
placeholder="email"
/>
<mat-icon matPrefix>email</mat-icon>
@if( (registerForm.controls.email.dirty || form.submitted) &&
!registerForm.controls.email.valid ) {
<mat-error>Please provide a valid email</mat-error>
}
</mat-form-field>
<mat-form-field>
<mat-label>password</mat-label>
<input
matNativeControl
formControlName="password"
type="password"
placeholder="password"
/>
<mat-icon matPrefix>lock</mat-icon>
@if( (registerForm.controls.password.dirty || form.submitted) &&
!registerForm.controls.password.valid ){
<mat-error>Password must be at least 8 characters long</mat-error>
}
</mat-form-field>
<mat-form-field>
<mat-label>confirm password</mat-label>
<input
matNativeControl
formControlName="confirmPassword"
type="password"
placeholder="confirm password"
/>
<mat-icon matPrefix>lock</mat-icon>
@if( (registerForm.controls.confirmPassword.dirty || form.submitted) &&
registerForm.hasError('passwordMatch') ){
<mat-error>Must match password field</mat-error>
}
</mat-form-field>
@if (status() === 'error'){
<mat-error>Could not create account with those details.</mat-error>
} @else if(status() === 'creating'){
<mat-spinner diameter="50"></mat-spinner>
}
<button
mat-raised-button
color="accent"
type="submit"
[disabled]="status() === 'creating'"
>
Submit
</button>
</form>