Validate fields after user has left a field

FormsAngularjs

Forms Problem Overview


With AngularJS, I can use ng-pristine or ng-dirty to detect if the user has entered the field. However, I want to do client-side validation only after the user has left the field area. This is because when a user enters a field like e-mail or phone, they will always get an error thrown until they've completed typing out their full e-mail, and this is not an optimal user experience.

Example


UPDATE:

Angular now ships with a custom blur event: https://docs.angularjs.org/api/ng/directive/ngBlur

Forms Solutions


Solution 1 - Forms

From version 1.3.0 this can easily be done with $touched, which is true after the user has left the field.

<p ng-show="form.field.$touched && form.field.$invalid">Error</p>

https://docs.angularjs.org/api/ng/type/ngModel.NgModelController

Solution 2 - Forms

Angular 1.3 now has ng-model-options, and you can set the option to { 'updateOn': 'blur'} for example, and you can even debounce, when the use is either typing too fast, or you want to save a few expensive DOM operations (like a model writing to multiple DOM places and you don't want a $digest cycle happening on every key down)

https://docs.angularjs.org/guide/forms#custom-triggers and https://docs.angularjs.org/guide/forms#non-immediate-debounced-model-updates

> By default, any change to the content will trigger a model update and > form validation. You can override this behavior using the > ngModelOptions directive to bind only to specified list of events. > I.e. ng-model-options="{ updateOn: 'blur' }" will update and validate > only after the control loses focus. You can set several events using a > space delimited list. I.e. ng-model-options="{ updateOn: 'mousedown > blur' }"

And debounce

> You can delay the model update/validation by using the debounce key > with the ngModelOptions directive. This delay will also apply to > parsers, validators and model flags like $dirty or $pristine. > > I.e. ng-model-options="{ debounce: 500 }" will wait for half a second > since the last content change before triggering the model update and > form validation.

Solution 3 - Forms

I solved this by expanding on what @jlmcdonald suggested. I created a directive that would automatically be applied to all input and select elements:

var blurFocusDirective = function () {
    return {
        restrict: 'E',
        require: '?ngModel',
        link: function (scope, elm, attr, ctrl) {
            if (!ctrl) {
                return;
            }

            elm.on('focus', function () {
                elm.addClass('has-focus');

                scope.$apply(function () {
                    ctrl.hasFocus = true;
                });
            });

            elm.on('blur', function () {
                elm.removeClass('has-focus');
                elm.addClass('has-visited');

                scope.$apply(function () {
                    ctrl.hasFocus = false;
                    ctrl.hasVisited = true;
                });
            });

            elm.closest('form').on('submit', function () {
                elm.addClass('has-visited');

                scope.$apply(function () {
                    ctrl.hasFocus = false;
                    ctrl.hasVisited = true;
                });
            });

        }
    };
};

app.directive('input', blurFocusDirective);
app.directive('select', blurFocusDirective);

This will add has-focus and has-visited classes to various elements as the user focuses/visits the elements. You can then add these classes to your CSS rules to show validation errors:

input.has-visited.ng-invalid:not(.has-focus) {
    background-color: #ffeeee;   
}

This works well in that elements still get $invalid properties etc, but the CSS can be used to give the user a better experience.

Solution 4 - Forms

I managed to do this with a pretty simple bit of CSS. This does require that the error messages be siblings of the input they relate to, and that they have a class of error.

:focus ~ .error {
    display:none;
}

After meeting those two requirements, this will hide any error message related to a focused input field, something that I think angularjs should be doing anyway. Seems like an oversight.

Solution 5 - Forms

This seems to be implemented as standard in newer versions of angular.

The classes ng-untouched and ng-touched are set respectively before and after the user has had focus on an validated element.

CSS

input.ng-touched.ng-invalid {
   border-color: red;
}

Solution 6 - Forms

Regarding @lambinator's solution... I was getting the following error in angular.js 1.2.4:

> Error: [$rootScope:inprog] $digest already in progress

I'm not sure if I did something wrong or if this is a change in Angular, but removing the scope.$apply statements resolved the problem and the classes/states are still getting updated.

If you are also seeing this error, give the following a try:

var blurFocusDirective = function () {
  return {
    restrict: 'E',
    require: '?ngModel',
    link: function (scope, elm, attr, ctrl) {
      if (!ctrl) {
        return;
      }
      elm.on('focus', function () {
        elm.addClass('has-focus');
        ctrl.$hasFocus = true;
      });

      elm.on('blur', function () {
        elm.removeClass('has-focus');
        elm.addClass('has-visited');
        ctrl.$hasFocus = false;
        ctrl.$hasVisited = true;
      });

      elm.closest('form').on('submit', function () {
        elm.addClass('has-visited');

        scope.$apply(function () {
          ctrl.hasFocus = false;
          ctrl.hasVisited = true;
        });
      });
    }
  };
};
app.directive('input', blurFocusDirective);
app.directive('select', blurFocusDirective);

Solution 7 - Forms

It might work for you to write a custom directive that wraps the javascript blur() method (and runs a validation function when triggered); there's an Angular issue that has a sample one (as well as a generic directive that can bind to other events not natively supported by Angular):

https://github.com/angular/angular.js/issues/1277

If you don't want to go that route, your other option would be to set up $watch on the field, again triggering validation when the field is filled out.

Solution 8 - Forms

To pick up further on the given answers, you can simplify input tagging by using CSS3 pseudo-classes and only marking visited fields with a class to delay displaying validation errors until the user lost focus on the field:

(Example requires jQuery)

JavaScript

module = angular.module('app.directives', []);
module.directive('lateValidateForm', function () {
    return {
        restrict: 'AC',
        link: function (scope, element, attrs) {
            $inputs = element.find('input, select, textarea');
      
            $inputs.on('blur', function () {
                $(this).addClass('has-visited');
            });

            element.on('submit', function () {
                $inputs.addClass('has-visited');
            });
        }
    };
});

CSS

input.has-visited:not(:focus):required:invalid,
textarea.has-visited:not(:focus):required:invalid,
select.has-visited:not(:focus):required:invalid {
  color: #b94a48;
  border-color: #ee5f5b;
}

HTML

<form late-validate-form name="userForm">
  <input type="email" name="email" required />
</form>

Solution 9 - Forms

based on @nicolas answer.. Pure CSS should the trick, it will only show the error message on blur

<input type="email" id="input-email" required
               placeholder="Email address" class="form-control" name="email"
               ng-model="userData.email">
        <p ng-show="form.email.$error.email" class="bg-danger">This is not a valid email.</p>

CSS

.ng-invalid:focus ~ .bg-danger {
     display:none;
}

Solution 10 - Forms

Here is an example using ng-messages (available in angular 1.3) and a custom directive.

Validation message is displayed on blur for the first time user leaves the input field, but when he corrects the value, validation message is removed immediately (not on blur anymore).

JavaScript

myApp.directive("validateOnBlur", [function() {
    var ddo = {
        restrict: "A",
        require: "ngModel",
        scope: {},
        link: function(scope, element, attrs, modelCtrl) {
            element.on('blur', function () {
                modelCtrl.$showValidationMessage = modelCtrl.$dirty;
                scope.$apply();
            });
        }
    };
    return ddo;
}]);

HTML

<form name="person">
	<input type="text" ng-model="item.firstName" name="firstName" 
		ng-minlength="3" ng-maxlength="20" validate-on-blur required />
	<div ng-show="person.firstName.$showValidationMessage" ng-messages="person.firstName.$error">
		<span ng-message="required">name is required</span>
		<span ng-message="minlength">name is too short</span>
		<span ng-message="maxlength">name is too long</span>
	</div>
</form>

PS. Don't forget to download and include ngMessages in your module:

var myApp = angular.module('myApp', ['ngMessages']);

Solution 11 - Forms

ng-model-options in AngularJS 1.3 (beta as of this writing) is documented to support {updateOn: 'blur'}. For earlier versions, something like the following worked for me:

myApp.directive('myForm', function() {
  return {
    require: 'form',
    link: function(scope, element, attrs, formController) {
      scope.validate = function(name) {
        formController[name].isInvalid
            = formController[name].$invalid;
      };
    }
  };
});

With a template like this:

<form name="myForm" novalidate="novalidate" data-my-form="">
<input type="email" name="eMail" required="required" ng-blur="validate('eMail')" />
<span ng-show="myForm.eMail.isInvalid">Please enter a valid e-mail address.</span>
<button type="submit">Submit Form</button>
</form>

Solution 12 - Forms

Use field state $touched The field has been touched for this as shown in below example.

<div ng-show="formName.firstName.$touched && formName.firstName.$error.required">
    You must enter a value
</div>

Solution 13 - Forms

You can dynamically set the has-error css class (assuming you're using bootstrap) using ng-class and a property on the scope of the associated controller:

plunkr: http://plnkr.co/edit/HYDlaTNThZE02VqXrUCH?p=info

HTML:

<div ng-class="{'has-error': badEmailAddress}">
    <input type="email" class="form-control" id="email" name="email"
        ng-model="email" 
        ng-blur="emailBlurred(email.$valid)">
</div>

Controller:

$scope.badEmailAddress = false;

$scope.emailBlurred = function (isValid) {
    $scope.badEmailAddress = !isValid;
};

Solution 14 - Forms

If you use bootstrap 3 and lesscss you can enable on blur validation with the following less snippet:

:focus ~ .form-control-feedback.glyphicon-ok {
  display:none;
}

:focus ~ .form-control-feedback.glyphicon-remove {
  display:none;
}

.has-feedback > :focus {
  & {
    .form-control-focus();
  }
}

Solution 15 - Forms

outI used a directive. Here is the code:

app.directive('onBlurVal', function () {
    return {
        restrict: 'A',
        link: function (scope, element, attrs, controller) {
          
            element.on('focus', function () {
                element.next().removeClass('has-visited');
                element.next().addClass('has-focus');
            });
               
            element.on('blur', function () {
               
                element.next().removeClass('has-focus');
                element.next().addClass('has-visited');
            });
        }
    }
})

All my input control has a span element as the next element, which is where my validation message is displayed and so the directive as an attribute is added to each input control.

I also have (optional).has-focus and has-visited css class in my css file which you see being referenced in the directive.

NOTE: remember to add 'on-blur-val' exactly this way to your input control without the apostrophes

Solution 16 - Forms

By using ng-focus you can achieve your goal. you need to provide ng-focus in your input field. And while writing your ng-show derivatives you have to write a logic not equal too. Like the below code:

<input type="text" class="form-control" name="inputPhone" ng-model="demo.phoneNumber" required ng-focus> <div ng-show="demoForm.inputPhone.$dirty && demoForm.inputPhone.$invalid && !demoForm.inputPhone.$focused"></div>

Solution 17 - Forms

We can use onfocus and onblur functions. Would be simple and best.

<body ng-app="formExample">
  <div ng-controller="ExampleController">
  <form novalidate class="css-form">
    Name: <input type="text" ng-model="user.name" ng-focus="onFocusName='focusOn'" ng-blur="onFocusName=''" ng-class="onFocusName" required /><br />
    E-mail: <input type="email" ng-model="user.email" ng-focus="onFocusEmail='focusOn'" ng-blur="onFocusEmail=''" ng-class="onFocusEmail" required /><br />
  </form>
</div>

<style type="text/css">
 .css-form input.ng-invalid.ng-touched {
    border: 1px solid #FF0000;
    background:#FF0000;
   }
 .css-form input.focusOn.ng-invalid {
    border: 1px solid #000000;
    background:#FFFFFF;
 }
</style>

Try here:

http://plnkr.co/edit/NKCmyru3knQiShFZ96tp?p=preview

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionmcranston18View Question on Stackoverflow
Solution 1 - Formsuser1506145View Answer on Stackoverflow
Solution 2 - FormspocesarView Answer on Stackoverflow
Solution 3 - FormslambinatorView Answer on Stackoverflow
Solution 4 - FormsnicholasView Answer on Stackoverflow
Solution 5 - FormsArg0nView Answer on Stackoverflow
Solution 6 - FormsmelcherView Answer on Stackoverflow
Solution 7 - FormsjlmcdonaldView Answer on Stackoverflow
Solution 8 - FormsPatrick GlandienView Answer on Stackoverflow
Solution 9 - FormskeithicsView Answer on Stackoverflow
Solution 10 - FormsjurgemarView Answer on Stackoverflow
Solution 11 - FormsTerence BandoianView Answer on Stackoverflow
Solution 12 - FormsKushal SharmaView Answer on Stackoverflow
Solution 13 - FormsMikeView Answer on Stackoverflow
Solution 14 - FormsStefanView Answer on Stackoverflow
Solution 15 - Formsuser3676608View Answer on Stackoverflow
Solution 16 - FormsRayView Answer on Stackoverflow
Solution 17 - FormsNaveen KumarView Answer on Stackoverflow