Angular2 - FormControl Validation on blur

89,875

Solution 1

EDIT 2

As Alex and the official documentation says, Angular version 5.0.0 has new option for your ngModel updateOn: 'blur'

this.email = new FormControl(null, {
   validators: Validators.required,
   updateOn: 'blur'
});

Also you can use other update options: change (default), blur, submit.


Original

I use directive where remove whole validation on focus and return it back after blur event. It based on Cristian Deschamps answer.

I update validity only on blur, so if value was invalid before focus it will be invalid after. But if you start input, validity will be updated.

For some reasons clearing order make sense, so I clear async validators first.

Any provided suggestion will be helpful =)

import { Directive } from '@angular/core';
import { NgControl } from '@angular/forms';

@Directive({
  selector: '[validate-onblur]',
  host: {
    '(focus)': 'onFocus($event)',
    '(blur)': 'onBlur($event)'
  }
})
export class ValidateOnBlurDirective {
    private validators: any;
    private asyncValidators: any;
    constructor(public formControl: NgControl) {
    }
    onFocus($event) {
      this.validators = this.formControl.control.validator;
      this.asyncValidators = this.formControl.control.asyncValidator;
      this.formControl.control.clearAsyncValidators();
      this.formControl.control.clearValidators();
    }

    onBlur($event) {
      this.formControl.control.setAsyncValidators(this.asyncValidators);
      this.formControl.control.setValidators(this.validators);
      this.formControl.control.updateValueAndValidity();
    }
}

Also, please stay tuned on this Angular 2 github thread about onBlur validation


EDIT 1

There is another problem - if I just click on the field and after click away - validation will be called. If you have any notification about it (or server calls) - it would appear every time you do it. So you can add wasChanged property and use it like this:

    @Directive({
        selector: '[validate-onblur]',
        host: {
            '(focus)': 'onFocus($event)',
            '(blur)': 'onBlur($event)',
            '(keyup)': 'onKeyup($event)',
            '(change)': 'onChange($event)',
            '(ngModelChange)': 'onNgModelChange($event)'
        }
    })
    export class ValidationOnBlurDirective {
        private validators: any;
        private asyncValidators: any;
        private wasChanged: any;
        constructor(public formControl: NgControl) {
        }
        onFocus($event) {
            this.wasChanged = false;
            this.validators = this.formControl.control.validator;
            this.asyncValidators = this.formControl.control.asyncValidator;
            this.formControl.control.clearAsyncValidators();
            this.formControl.control.clearValidators();
        }
        onKeyup($event) {
            this.wasChanged = true; // keyboard change
        }
        onChange($event) {
            this.wasChanged = true; // copypaste change
        }
        onNgModelChange($event) {
            this.wasChanged = true; // ng-value change
        }
        onBlur($event) {
            this.formControl.control.setAsyncValidators(this.asyncValidators);
            this.formControl.control.setValidators(this.validators);
            if (this.wasChanged)
                this.formControl.control.updateValueAndValidity();
        }
    }

Solution 2

As of Angular v 5.0.0 this is now possible by marking updateOn: 'blur' to the form control.

This also means that valueChanges does not fire for that form control until the blur event occurs. Here's an example wiht minlength together with required:

this.form = new FormGroup({
  username: new FormControl('', {
  validators: [Validators.required, Validators.minLength(6)], updateOn: 'blur'} )
})

get username() {
  return this.form.get('username');
}

In the template you'd want to mark that the validation message won't show unless the field is touched:

<div *ngIf="username.hasError('minlength') || username.hasError('required') 
              && username.touched">Required and minlength 6!
</div>

DEMO

P.S If needed you can also mark this on the whole form, not only on a specific form control

Solution 3

Found a way, in rc6.

1- Create a directive: validate-onblur.directive.ts

@Directive({
  selector: '[validate-onblur]',
  host: {
    '(focus)': 'onFocus($event)',
    '(blur)': 'onBlur($event)'
  }
})
export class ValidateOnBlurDirective {
    constructor(public formControl: NgControl) {
    }

    onFocus($event) {
      this.formControl.control.markAsUntouched(false);
    }

    onBlur($event) {
      this.formControl.control.markAsTouched(true);
    }
}

Then in your html template just add the directive to your form, my example use the ReactiveFormsModule model.

Then add this to your error message:

<input type="text" formControlName="full_name" validate-onblur />

<span *ngIf="formAccountDetails.controls.full_name.touched && !formAccountDetails.controls.full_name.valid && !formAccountDetails.controls.full_name.pristine" class="errors">
        ...
</span>

Solution 4

Something like this: Use property touched of the ngControl object.

 <div class="form-group" [class.has-error]="!name.valid && name.touched">
        <label for="name">Name</label>
        <input #name="ngForm" ngControl="name" name="name" type="text" class="form-control" required>
 </div>

Solution 5

I have improved a little bit Alex Shestakov solution, which already worked by the way, in order to avoid setting the control state to valid when its value changes while it keeps the focus.

@Directive({
    selector: '[validate-onblur]',
    host: {
        '(focus)': 'onFocus($event)',
        '(blur)' : 'onBlur($event)'
    }
})
export class ValidateOnBlurDirective {

    private validators: any;
    private asyncValidators: any;
    private hasFocus = false;

    constructor(public formControl: NgControl) {
    }

    onFocus($event) {
        this.hasFocus = true;
        this.validators = this.formControl.control.validator;
        this.asyncValidators = this.formControl.control.asyncValidator;
        this.formControl.control.clearAsyncValidators();
        this.formControl.control.clearValidators();
        this.formControl.control.valueChanges
            .filter(() => this.hasFocus)
            .subscribe(() => this.formControl.control.markAsPending());
    }

    onBlur($event) {
        this.hasFocus = false;
        this.formControl.control.setAsyncValidators(this.asyncValidators);
        this.formControl.control.setValidators(this.validators);
        this.formControl.control.updateValueAndValidity();
    }
}

This way the control will remain in pending state as long as it keeps the focus. That will help to avoid the case when a control is invalid before it gets the focus and then as soon as the user starts typing on it, it is marked as valid, before the blur event happens, when validators are set again and real validity of the control should be determined.

Share:
89,875
Reece Thompson
Author by

Reece Thompson

Updated on July 09, 2022

Comments

  • Reece Thompson
    Reece Thompson almost 2 years

    I'm looking at adding some basic email validation to check that the user has put in a correct email address. Currently using the method below, the validation updates as the user types, which looks odd when it errors after entering one character.

    validEmail(c: Control){
    if(!c.value.match('[a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?')){
      return {
        validEmail: true
      };
    }
    return null;
    }    
    
    ctrlEmailAddress: Control = new Control('', Validators.compose([
    Validators.required, this.validEmail]));
    

    I was wondering if it is possible to trigger the validation on blur of the field, like in angularJS with:

    ng-model-options="{ updateOn: 'blur' }"
    

    I'm aware of the blur option on the input field within the html but this doesn't put my control in error unless there is a way to put the control into an error state.

    Could anyone help point me in the right direction?

    Thanks.

    Edit: I'm looking for a angular2 solution, not an angularJS solution.

  • user5260143
    user5260143 over 7 years
    This will not help at the next time when element get focus. name.focus getting its true value at the first touch, and from second time - the validation indicator will be disaplyed at each key-press.
  • Daniel Francisco Sabugal
    Daniel Francisco Sabugal over 7 years
    The problem with this approach is that if the control is invalid and dirty when it gets the focus, then after the first key stroke (I'm thinking of an input text box) it will be valid without validators, and the ng-valid class will be applied. If you style that class to show the input box as green for example, that's a problem. I think it should keep somehow pending state as long as the control keeps the focus, so ng-valid is not applied.
  • Alex Shestakov
    Alex Shestakov over 7 years
    @DanielFranciscoSabugal Yes, it can be a problem, but in my case it was good decision. User should not see an errors, while he makes input, and it's will be validated after he ends.
  • snowBlind
    snowBlind almost 7 years
    @AlexShestakov Thanks for this post. For me the this.formControl.control.validator (and asyncValidator) comes back null. I'm doing this on an existing reactive form with working validation. Any ideas?
  • snowBlind
    snowBlind almost 7 years
    It seems I was trying this.formControl.control.validators [plural] instead of this.formControl.control.validator
  • Programmer
    Programmer about 6 years
    When using this syntax, the second param in the FormControl constructor is of type AbstractControlOptions, and async validators are set like this: this.form = new FormGroup({ username: new FormControl('', { validators: [Validators.required, Validators.minLength(6)], asyncValidators: yourAsyncFunction, updateOn: 'blur'} ) })
  • zer09
    zer09 almost 6 years
    how this should be done if you are using FormBuilder?
  • zer09
    zer09 almost 6 years
    how this should be done if you are using FormBuilder?
  • Manu Chadha
    Manu Chadha almost 6 years
    @zer09 - were you able to find out how to use updateOn with FormBuilder?
  • zer09
    zer09 almost 6 years
    @ManuChadha nope, this still have issue on github
  • Shachar Har-Shuv
    Shachar Har-Shuv over 5 years
    That does not answer the question. The question asks how to validate only on blur.
  • Snowbases
    Snowbases over 4 years
    anyone want it to work with Validator.compose() can refer this stackoverflow.com/questions/50103342/…