96

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

isherwood
  • 58,414
  • 16
  • 114
  • 157
mcranston18
  • 4,680
  • 3
  • 36
  • 37
  • 3
    I guess that this is something angularjs should have supported... – csg Feb 05 '14 at 20:33
  • Maybe. The built in form validation works quite well that I don't know whether I would want this built in. After all, most use cases I want Angular to validate immediately. i.e. in a number field, I want the form to invalidate immediately if the user begins typing letters. – mcranston18 Feb 06 '14 at 00:19
  • 11
    validation on blur is common since 15 years... – Olivvv Mar 17 '14 at 15:49

17 Answers17

93

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

user1506145
  • 5,176
  • 11
  • 46
  • 75
  • 1
    Wouldn't that still execute the validation after each key-up? OP stated he wanted to do validation only after the user has left the field. – comecme Apr 06 '15 at 11:09
  • @comecme yes it would, the default ng-model-options is `updateOn: 'default'` which means, for inputs, key up, and for selects, on change – pocesar May 18 '15 at 12:22
  • 5
    The big flaw here, though, is that you won't have the same validation experience if you come back to the field. You're back at square one, where it validates on every keystroke, since it's already set to touched. I'm surprised this got so many upvotes. – dudewad Sep 14 '15 at 06:40
  • 3
    @dudewad that is correct, but I think it's the desired behavior UX wise - if you come back to an invalid field, the error message should persist until the value is valid, not until you typed the first letter. – danbars Mar 21 '16 at 13:55
  • @dudewad Wouldn't it help the user to validate on each press after they already know a field was invalid after the first time they filled it in? Once they know the field was invalid, it is likely they would want to quickly know when the field is valid and validating on each key press would speed up that process maybe? – Alan Dunning Sep 14 '16 at 22:48
  • So $touched is true only after the user has left the field ? From the Angular documentation I thought it was true when the user entered the field. I'm assuming the default behavior, not modified by any ng-model-options. What is the sensor for simply entering the field ? – Stephane Oct 20 '16 at 09:19
75

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.

pocesar
  • 6,860
  • 6
  • 56
  • 88
  • 2
    In most cases this does not resolve the issue, because you probably still want to receive `ng-change` events before the user left the field. For example, in my case I request new data from my API as soon as the input field is valid, but I don't want to show an error message as soon as it is invalid -- I only want to do this when it is invalid and the blur event occured. – Tom Jul 18 '14 at 06:55
  • @Tom I'm testing `1.3.0 rc-3` and it does not fire `change` until `blur`, check this: http://plnkr.co/edit/RivVU3r7xfHO1hUybqMu?p=preview – Gill Bates Oct 01 '14 at 08:04
  • This should really be the accepted, top answer. Why add a directive, when you can add a single property? – Dan Hulton Jun 02 '15 at 00:40
32

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.

lambinator
  • 10,616
  • 7
  • 55
  • 57
  • 2
    Why is '?ngModel' required instead of simply 'ngModel'? I understand requiring ngModel to be there, but why the question mark? – Noremac Nov 21 '13 at 15:20
  • 2
    Thanks for the idea. Should point out that it requires jQuery (I think - couldn't see a .closest() in jql). – iandotkelly Nov 24 '13 at 23:35
  • 1
    Not all elements are supported by ngModel (e.g. input type=submit). The ? requires ngModel only on elements that support it. – chmanie Dec 04 '13 at 22:00
  • 5
    To be honest, you should not deviate from the angular way of setting input field states like `formName.inputName.$dirty`. I'd suggest editing the directive to write a similar boolean like `formName.inputName.$focused`, rather than using class names for blur and booleans for validation. – Tom Jul 18 '14 at 06:57
17

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.

nicholas
  • 14,184
  • 22
  • 82
  • 138
  • 2
    Ahh. This is a smart way to go. I much prefer this over writing javascript to do it. – deck May 02 '14 at 21:13
  • 5
    The downside of this approach is that when someone is correcting an error the error is no longer visible which isn't ideal. Ideal would be that the error doesn't show until blur but doesn't disappear until it is corrected. – Chris Nicola Jul 31 '14 at 18:30
5

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;
}
Arg0n
  • 8,283
  • 2
  • 21
  • 38
4

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);
isherwood
  • 58,414
  • 16
  • 114
  • 157
melcher
  • 1,543
  • 9
  • 15
2

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.

jlmcdonald
  • 13,408
  • 2
  • 54
  • 64
2

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>
Patrick Glandien
  • 7,791
  • 5
  • 41
  • 47
2

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;
}
keithics
  • 8,576
  • 2
  • 48
  • 35
2

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']);
jurgemar
  • 21
  • 1
1

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>
1

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>
Kushal Sharma
  • 220
  • 1
  • 2
  • 10
0

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;
};
matteosister
  • 268
  • 2
  • 10
Mike
  • 21
  • 1
  • 2
0

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();
  }
}
Stefan
  • 21
  • 3
0

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

0

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>

Ray
  • 1
  • 2
0

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

Naveen Kumar
  • 197
  • 1
  • 13