Building an Angular application that involves many forms can be stressful. Especially so when you have to handle the validation messages on each component.
One way to reduce your stress is to write a generic validation class that handles all your validation messages.
On the one hand, this will significantly reduce the code on your HTML template. It will also give you one source of error messages with the flexibility to override the error message on each component
On the other, it involves writing a little more code on the component and extra files
But I think the pros outweigh the cons when dealing with multiple forms in different components.
Prerequisites
Basic knowledge of Angular
Basic knowledge of reactive forms
What we are building
Angular has two types of forms: template driven forms and reactive forms. In this post, we will focus on reactive forms.
We will learn how to validate a simple Login and Sign up form with generic validation using a reactive form. I used the Bulma CSS framework for the design.
The form input values are just console log when you click on submit. I did this so that we can focus mainly on form validation, but you can do whatever you want with the form input values.
I have created a starter file for this project with all the HTML, CSS, and Bulma done. This allows us to focus more on the generic form validation. Clone this repo on GitHub here.
Open the generic-reactive-form-validation folder in any of your editors. The file structure should look like this:
Step 2: Import ReactiveFormsModule
Now, let's import ReactiveFormsModule into our app module and add it to the imports array.
Step 3: Create a generic validation class and password confirmation validator
Generic validation class
Let's create a shared folder inside the root app folder. Then inside the shared folder, create a generic-validator.ts file. Write the following code:
First, we import the FormGroup. We can write all our validation messages in this file or pass each form validation message from their component.
Each property on the VALIDATION_MESSAGES object corresponds to each input field name or formControlName. Also, each property of the input field corresponds to the validation name on it. Its value is what you want to show as the error message.
For instance, the input field name formControlName "email" has validations of “required” and “email” on it.
In constructor method,we can override the default error messages from the component where our generic validation is being used by passing the validation message when we instantiate our generic validation class.
The processMessages methodprocesses each form input field and returns the error message to display.
Password confirmation validation
Now, let’s create a password confirmation validator to check if our password and confirm password match.
Inside the shared folder, create a password-matcher.ts file. Write the following code:
Step 4: Add FormGroup and FormBuilder to each components and templates
Sign up form component
Inside the app/modules/sign-up, add the below code to the sign-up component:
We have Angular in-built validations for each input field along with our custom PasswordMatcher validation to ensure that the password and confirmed password match.
Sign up form template
Now let's have a look at the sign up form template:
We also added formControlName to each input field. If the display message has a firstName error message it will apply the ngClass is-danger to the input field.
We disable the submit button if the form is not valid.
Login form component
Inside the app/modules/login, add the below code to the login component:
The only difference here with the sign up component is that we will be overriding the default error message in our generic validation class with the validation message.
Login form template
Write the following code in the login template:
Step 5: Use Generic Validation in each component
Generic validation on Sign up
Add the following code to the sign-up.component.ts file:
Here we have imported the generic validation class.
We added the @ViewChildren to access every form input field in our signup HTML file. This helps us listen for an event on them.
private genericValidator: GenericValidator;
constructor(private fb: FormBuilder) {
// Define an instance of the validator for use with this form
this.genericValidator = new GenericValidator();
}
We instantiate the Generic validation inside the constructor.
Then, we implement the ngAfterViewInit interface.
ngAfterViewInit(): void {
// Watch for the blur event from any
// input element on the form.
const controlBlurs: Observable<any>[] = this.formInputElements
.map((formControl: ElementRef) =>
fromEvent(formControl.nativeElement, 'blur')
);
// Merge the blur event observable
// with the valueChanges observable
merge(this.signupForm.valueChanges, ...controlBlurs)
.pipe(debounceTime(800))
.subscribe(value => {
this.displayMessage = this.genericValidator
.processMessages(this.signupForm);
});
}
Here we watch for the blur event from any input element on the form.
Now we have combined the form value changes observable (which gets triggered when any of the input values change) and the blur events of any input field into one observable.
So, when a user changes an input value or taps into any input field, this merge method gets triggered.
Then we add a delay of 800 milliseconds with debounceTime(800). This gives the user time to make changes before trigging the validation.
Finally, we get the error messages to display by calling the generic validator method.
Generic validation on Login
Write the following code to the login.component.ts file:
The only difference here from the sign up code is that we are overriding our default validation messages with our new validation Messages specified in this component. Then we're passing it into the generic validation class when we instantiate it.
constructor(private fb: FormBuilder) {
// Defines all of the validation messages for the form.
this.validationMessages = {
email: {
required: 'Required',
email: 'This email is invalid'
},
password: {
required: 'Required',
minlength: 'The password length must be greater than or equal to 8'
}
};
// Define an instance of the validator for use with this form,
// passing in this form's set of validation messages.
this.genericValidator = new GenericValidator(this.validationMessages);
}
We can expected this to work the same way as the sign up generic validation.
And that's all you need to build a generic validator in Angular.
Conclusion
Creating a generic validator makes it easy to handle multiple form validations without using a ton of redundant code in your Angular application.