Angular 2 Form Validation: How to fire form validation on blur?
Table Of Content
Every came across a situation where validation error is fired when a user is still typing on the form? Yeah. It is frustrating.
Although I think there should be an out of the box solution for this, currently there is none for Angular 2.
Update - November 11, 2017
Angular 5 has now added this functionality out of the box. You can check out this link to use the out of the box solution of validation and model update on blur.
But for people still working on Angular 2 projects- there is a workaround you could use. The solution is to fire validation error when use focus-out from the current field.
I know. It might not be the best solution. But it is at least a better user experience than getting interrupted by error messages when I am still on the field typing something.
So, let's see how we can fire validation errors on-blur in Angular 2. Basically, we want to leverage the touch
property of form controls and use to decide whether or not to show the error message.
Follow the complete demo here, for better understanding.
The first step is to create a directive which will handle focus
and focusout
event on a particular input field.
Validation On Blur directive
@Directive({ selector: '[validateOnBlur]', })
export class MyDirective { @Input('validateFormControl') validateFormControl;
constructor() { }
@HostListener('focus', ['$event.target']) onFocus(target) { console.log("Focus called"); this.validateFormControl.markAsUntouched(); console.log(this.validateFormControl.touched); }
@HostListener('focusout', ['$event.target']) onFocusout(target) { console.log("Focus out called"); this.validateFormControl.markAsTouched(); }
}
In the above code snippet, we are creating a directive called MyDirective
with [validateOnBlur]
as the selector. Which means, to use this directive declare validateOnBlur
as an attribute on that element. Like this:
<input name="date" type="text" validateOnBlur />
Next, we want to listen to the focus
and focusout
events on the input
element. For this, we use @HostListener
decorator. We will get into the implementation of these event handlers later.
If you want to get better understanding of what's happening under the hood- do read this gem which provides practical recipes for using Angular 2 in real world applications.
So, that's the overview of our directive. Simple, isn't it?
Now, let's move on to our user form which I have implemented in AppComponent
component.
Usage of Validation on Blur directive
<form class="login-form" [formGroup]="loginForm" novalidate (ngSubmit)="handleSubmit(loginForm.value, loginForm.valid)"> <input name="date" type="text" formControlName="username" validateOnBlur [validateFormControl]="loginForm.controls['username']"> <p *ngIf="loginForm.controls['username'].touched && loginForm.controls['username'].dirty && loginForm.controls['username'].invalid">Invalid</p></form>
I am using ReactiveFormsModule
module in this example. Please read this article on Model Drive Forms for better understanding.
The only element we want to focus here on is the input
element:
<input name="date" type="text" formControlName="username" validateOnBlur [validateFormControl]="loginForm.controls['username']" >
You can see how I am using the validateOnBlur
directive that I implemented above. The next attribute is [validateFormControl]
. It is actually a parameter I am passing to validateOnBlur
directive. And the value of that parameter is the form-control object.
Doing this will let me use all the nice properties of that particular control. In this case, we are only interested in the touched
property of that input
control.
Now, going back to our directive, let's see how we can toggle this property.
@HostListener('focus', ['$event.target'])onFocus(target) { console.log("Focus called"); this.validateFormControl.markAsUntouched();}
@HostListener('focusout', ['$event.target'])onFocusout(target) { console.log("Focus out called"); this.validateFormControl.markAsTouched();}
As you can see above, I am using markAsTouched
and markAsUntouched
functions on the form-control to toggle the touched
property of that control. I am just marking it as untouched
when I focus on the input control and then mark it as touched
when I am leaving the control.
Now, why I am doing this?
To understand this, let's see the following snippet of our user form:
<p *ngIf="loginForm.controls['username'].touched && loginForm.controls['username'].dirty && loginForm.controls['username'].invalid">Invalid</p>
The above p
tag is the validation error I want to display when the field it invalid. But, notice the ngIf
expression.
I am displaying the error only when the field is dirty, it is invalid and most importantly when the using is NOT touching
it. That is when the user is not still typing.
I am handling the touching
part in my validateOnBlur
directive. I am setting the field to untouched
when the user is still typing which hides the error message. And when the user has left the field I am setting that field to touched
. So now, if there are any validation errors those will be displayed on the field.
If you are new to the world of TypeScript this book will make your life easy when working on Angular 2 applications.
Summary:
Above is the extensive explanation of a super simple directive to fire validation messages on-blur event. This should definitely make your users feel more comfortable while filling up the forms.
Please not that if you are using Angular 5 the same above functionality is now available inbuilt. Check out this link to see how you can use it.
Please let me know what you think about the solution. Also, shoot a reply if you have a better one.