On the Bleeding Edge: Advanced AngularJS Form Validation

(Note: This article has been updated to use AngularJS 1.0.8 and Bootstrap 3.0.0. If you are using earlier versions the syntax may be slightly different.) We’ve been using AngularJS for the past few months and in general it has been really great. There has definitely been a learning curve, but the best part of the design is that it really encourages you to extend it properly. Whenever you try to hack something, it just feels wrong and you are forced to look at your problem from a different perspective. The benefit is the resulting solution is usually much more modular and portable.

I have had several occasions where I’ve had to write my own directives, providers, filters, etc.

Advanced Form Validation

One of the cool features of angular is the form validation. By decorating your field elements with the correct attributes, angular validation will occur automatically and let you know when a field (and in turn the form) is valid or invalid. In addition you can use some conditional css classes to highlight invalid fields to the user. Below is a simple example of a login form, where we keep the “Login” button (i.e. “call to action”) disabled until the form is valid:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
   <form name="loginForm" novalidate 
     ng-app="LoginApp" ng-controller="LoginController" ng-submit="login()">
     <div class="form-group">
       <input class="form-control" name="username" type="text" 
         placeholder="Username" required ng-model="session.username" />
       <span class="help-block" 
         ng-show="loginForm.username.$error.required">Required</span>
     </div>
     <div class="form-group">
       <input class="form-control" name="password" type="password" 
         placeholder="Password" required ng-model="session.password" />
       <span class="help-block" 
         ng-show="loginForm.password.$error.required">Required</span>
     </div>
     <div class="form-group">
         <button type="submit" class="btn btn-primary pull-right" 
           value="Login" title="Login" ng-disabled="!loginForm.$valid">
           <span>Login</span>
         </button>
     </div>
   </form>

Result:

The above example is functional and works fine. But in reality, its not the greatest user experience. Disabling a call to action like “Login” without a clear reason why can be confusing to the user, even in this simple example of required fields. So using AngularJS (and Bootstrap for easy styling) you can better highlight the fields that need attention:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   <form name="loginForm" novalidate 
     ng-app="LoginApp" ng-controller="LoginController" ng-submit="login()">
     <div class="form-group"
       ng-class="{'has-error': loginForm.username.$invalid}">
       <input class="form-control" name="username" type="text" 
         placeholder="Username" required ng-model="session.username" />
       <span class="help-block" 
         ng-show="loginForm.username.$error.required">Required</span>
     </div>
     <div class="form-group"
       ng-class="{'has-error': loginForm.password.$invalid}">
       <input class="form-control" name="password" type="password" 
         placeholder="Password" required ng-model="session.password" />
       <span class="help-block" 
         ng-show="loginForm.password.$error.required">Required</span>
     </div>
     <div class="form-group">
         <button type="submit" class="btn btn-primary pull-right" 
           value="Login" title="Login" ng-disabled="!loginForm.$valid">
           <span>Login</span>
         </button>
     </div>
   </form>

Result:

So now our controls are conditionally decorated with an ‘has-error‘ css class if they are currently invalid courtesy of the ‘ng-class‘ directive. But things still aren’t great. We still have a disabled call to action and even though we are highlighting the invalid fields, we are doing so right away. Before the user has done anything we are saying “You’ve done something wrong”. The solution for me was some custom directives. Let’s take a look at the disabled call to action first.

The reason we use the ‘ng-disabled‘ directive to disable the call to action is to prevent the form from being submitted when we know it to be invalid. We could remove the ‘novalidate‘ attribute on the form which will allow the built in browser validation to stop submission based on the ‘required‘ attribute, however there are several other non-standard html5 attributes we could potentially use to validate the field and the built-in browser validation would ignore those. So we basically want to allow for a submission to be attempted via the button click, but cancel it if the form is invalid. Enter ‘rcSubmit‘: (Note: all of our custom directives begin with our own prefix/namespace “rc”, for “RealCrowd”, to avoid any conflicts with other directives we may import)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    var rcSubmitDirective = {
        'rcSubmit': ['$parse', function ($parse) {
            return {
                restrict: 'A',
                require: 'form',
                link: function (scope, formElement, attributes, formController) {
 
                    var fn = $parse(attributes.rcSubmit);
 
                    formElement.bind('submit', function (event) {
                        // if form is not valid cancel it.
                        if (!formController.$valid) return false;
 
                        scope.$apply(function() {
                            fn(scope, {$event:event});
                        });
                    });
                }
            };
        }]
    };

As you can see the directive is pretty simple. It binds to the form’s submit event, and if the ngFormController is not valid, cancels the event. Otherwise it will execute the defined expression. This is basically a copy of the angular ngSubmit with the addition of a validation check. So we just replace the ‘ng-submit‘ attribute with the ‘rc-submit‘ and now we can remove the ‘ng-disabled‘ attribute from our login button because now the submit will not execute if the form is invalid.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   <form name="loginForm" novalidate 
     ng-app="LoginApp" ng-controller="LoginController" rc-submit="login()">
     <div class="form-group"
       ng-class="{'has-error': loginForm.username.$invalid}">
       <input class="form-control" name="username" type="text" 
         placeholder="Username" required ng-model="session.username" />
       <span class="help-block" 
         ng-show="loginForm.username.$error.required">Required</span>
     </div>
     <div class="form-group"
       ng-class="{'has-error': loginForm.password.$invalid}">
       <input class="form-control" name="password" type="password" 
         placeholder="Password" required ng-model="session.password" />
       <span class="help-block" 
         ng-show="loginForm.password.$error.required">Required</span>
     </div>
     <div class="form-group">
         <button type="submit" class="btn btn-primary pull-right" 
           value="Login" title="Login">
           <span>Login</span>
         </button>
     </div>
   </form>

Okay, so not much changed there really. The disabled call to action though always bugs me. And once we solve our second problem, it will show off its usefulness a bit more. The next problem is we are highlighting invalid fields before the user has done anything which could be a bit confusing. Ideally we should only highlight the fields if the user has interacted with the form. AngularJS has some built-in support for this as well. On the ngFormController there is a $dirty property and $pristine (basically the opposite of $dirty) property. So we can add that as an additional condition on our ‘error‘ css class. Something like:

ng-class="{'has-error': loginForm.username.$invalid && loginForm.username.$dirty}"

But this still isn’t exactly what we want. The field will get highlighted after it’s dirty (i.e. the user has interacted with it), but what if we attempt submission? Technically the form isn’t dirty so the field won’t be highlighted. We need to track another state of the form which isn’t currently being tracked: attempted submission. In the ideal scenario, erroneous fields should only be highlighted if the user has tried to interact with the field or if the user has attempted to submit the form. First, lets track attempted. We could try to expand on the current custom rcSubmit directive, but I can foresee using this directive independent of it at some point so I’m going to make a separate directive, ‘rcAttempt‘:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    var rcAttemptDirective = {
        'rcAttempt': function () {
            return {
                restrict: 'A',
                controller: ['$scope', function ($scope) {
                    this.attempted = false;
 
                    this.setAttempted = function() {
                        this.attempted = true;
                    };
                }],
                link: function(scope, formElement, attributes, attemptController) {
                    formElement.bind('submit', function (event) {
                        attemptController.setAttempted();
                        if (!scope.$$phase) scope.$apply();
                    });
                }
            };
        }
    };

In the code above you can see we create a custom controller with a property to track if form submission has been attempted. Then, like our rcSubmit, we bind to the form’s submit event, and set the ‘attempted‘ flag there. We can add this directive to our form element, but it won’t really do anything at this point. We are only tracking the state, but are not doing anything with it. We need a way access this information in the markup just like other form properties. The way to do that is by putting our new controller on the scope:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    var rcAttemptDirective = {
        'rcAttempt': function () {
            return {
                restrict: 'A',
                controller: ['$scope', function ($scope) {
                    this.attempted = false;
 
                    this.setAttempted = function() {
                        this.attempted = true;
                    };
                }],
                compile: function(cElement, cAttributes, transclude) {                    return {                        pre: function(scope, formElement, attributes, attemptController) {                            scope.rc = scope.rc || {};                            scope.rc[attributes.name] = attemptController;                        },                        post: function(scope, formElement, attributes, attemptController) {                            formElement.bind('submit', function (event) {                                attemptController.setAttempted();                                if (!scope.$$phase) scope.$apply();                            });                        }                    };                }            };
        }
    };

We use the ‘compile‘ function to manipulate the scope just as we would if we manipulate the DOM on ‘compile‘. We add an ‘rc‘ object, again to avoid conflicts with other scope values then, mimicking the ngFormController, use the form name. So when we access it in the markup it will look like: ‘rc.loginForm‘. We also move the binding to the post-linking function of ‘compile‘ because if you have a ‘compile‘ defined, the ‘link‘ function with be ignored. So now that the hard work is done we just need to add the directive and change our ‘ng-class‘ to use the new attempted state we are tracking:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
   <form name="loginForm" novalidate 
     ng-app="LoginApp" ng-controller="LoginController" 
     rc-attempt rc-submit="login()">
     <div class="form-group" 
       ng-class="{'has-error': loginForm.username.$invalid && 
         (loginForm.username.$dirty || rc.loginForm.attempted)}">
       <input class="form-control" name="username" type="text" 
         placeholder="Username" required ng-model="session.username" />
       <span class="help-block" 
         ng-show="loginForm.username.$error.required">Required</span>
     </div>
     <div class="form-group" 
       ng-class="{'has-error': loginForm.password.$invalid &&
         (loginForm.password.$dirty || rc.loginForm.attempted)}">
       <input class="form-control" name="password" type="password" 
         placeholder="Password" required ng-model="session.password" />
       <span class="help-block" 
         ng-show="loginForm.password.$error.required">Required</span>
     </div>
     <div class="form-group">
         <button type="submit" class="btn btn-primary pull-right" 
           value="Login" title="Login">
           <span>Login</span>
         </button>
     </div>
   </form>

Success! So now if we open the page fresh, the form is clean. If the user manipulates a particular field and leaves it invalid, only that particular field will be highlighted. If the user attempts to submit the form, all invalid fields will be highlighted. Here is a link to the Plunker to see it in action: View Plunker.

Custom directives in AngularJS can be very powerful, and the design of AngularJS encourages building modular components. You can see this in production on all of our forms, such as the Register form.

Note: The source for all the directives and examples can be found on our public GitHub repo: https://github.com/realcrowd/angularjs-utilities

Quick Update: Refinement and Bonus Method. As with most code, we have refined and enhanced as we’ve moved forward. Here are a couple small changes we’ve made since writing this blog. First, the separation of rcAttempt and rcSubmit ended up being unduly complex for our needs. We therefore moved the functionality in rcAttempt into rcSubmit so we now just need one directive:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
    var rcSubmitDirective = {        'rcSubmit': function () {            return {                restrict: 'A',                require: ['rcSubmit', '?form'],                controller: ['$scope', function ($scope) {                    this.attempted = false;                     this.setAttempted = function() {                        this.attempted = true;                    };                }],                compile: function(cElement, cAttributes, transclude) {                    return {                        pre: function(scope, formElement, attributes, controllers) {                             var submitController = controllers[0];                             scope.rc = scope.rc || {};                            scope.rc[attributes.name] = submitController;                        },                        post: function(scope, formElement, attributes, controllers) {                             var submitController = controllers[0];                            var formController = (controllers.length > 1) ?                                                  controllers[1] : null;                             var fn = $parse(attributes.rcSubmit);                             formElement.bind('submit', function (event) {                                submitController.setAttempted();                                if (!scope.$$phase) scope.$apply();                                 if (!formController.$valid) return false;                                 scope.$apply(function() {                                    fn(scope, {$event:event});                                });                            });                        }                    };                }            };        }    };

We now no longer need the rc-attempt attribute in our mark-up. Second, we noticed repetitive pattern in our class logic and decided to make a small utility method on our rcSubmitController (formly rcAttemptController) to make the markup a little cleaner. That method needs some additional information to, so we’ll need the following changes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    var rcSubmitDirective = {
        'rcSubmit': function () {
            return {
                restrict: 'A',
                require: ['rcSubmit', '?form'],
                controller: ['$scope', function ($scope) {
                    var formController = null;                     this.setFormController = function(controller) {                        formController = controller;                    };                     this.needsAttention = function (fieldModelController) {                        if (!formController) return false;                         if (fieldModelController) {                            return fieldModelController.$invalid &&                                    (fieldModelController.$dirty || this.attempted);                        } else {                            return formController && formController.$invalid &&                                   (formController.$dirty || this.attempted);                        }                    };

30
31
32
33
34
35
36
37
38
39
40
41
42
                compile: function(cElement, cAttributes, transclude) {
                    return {
                        pre: function(scope, formElement, attributes, controllers) {
 
                            var submitController = controllers[0];
 
                            var formController = (controllers.length > 1) ?                                                  controllers[1] : null;                            submitController.setFormController(formController); 
                            scope.rc = scope.rc || {};
                            scope.rc[attributes.name] = submitController;
                        },

In order for our utility method to work, we needed a reference to the ngFormController. Then our method accepts a ngModelController for a field and we do our logic. So now our class logic can be written as:

ng-class="{'has-error': rc.loginForm.needsAttention(loginForm.username)}

Just a couple small little bonuses to keep your code looking clean. The examples and code have been updated on Plunker and GitHub

  • Chris Nelson

    This is an awesome post, thanks for sharing. One question: could you explain the if (!scope.$$phase) scope.$digest()

    • Andy Norborg

      Thanks for the feedback! Regarding your question about the scope line. First from the docs:

      “$apply() is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). Because we are calling into the angular framework we need to perform proper scope life-cycle of exception handling, executing watches.” – http://docs.angularjs.org/api/ng.$rootScope.Scope

      Since we are updating the scope through a DOM event (submit), we need to make sure the change is applied. The ‘if (!scope.$$phase)’ part is to make sure the scope isn’t in the middle of digesting already. I picked it up in this stackoverflow post:

      http://stackoverflow.com/questions/12729122/prevent-error-digest-already-in-progress-when-calling-scope-apply

      It may not be necessary to check in this instance, but I’ve gotten into the habit of always checking to be on the safe side.

    • Andy Norborg

      I realized I mentioned ‘$apply()’ when your question was about digest. The same logic applies though, and I think technically from the documentation I should be doing a scope.$apply() instead of scope.$digest(). A scope.$apply() with force a $digest. I will update the post.

  • Pingback: On the Bleeding Edge: Advanced AngularJS Form V...

  • http://DeanSofer.com DJ

    No mention of ui-validate. I also don’t get why rc-submit is necessary, ng-submit already does a validity check.

    • Andy Norborg

      Are you referring to the AngularUI UI-Utils module? If so, I haven’t personally used it. As for ng-submit, I have not had the same experience. Here is a link to a plunkr using the ng-submit ( http://plnkr.co/edit/tEOGQtIS07ZpaBUmUE43?p=preview ). Even though I have the required attributes, it still appears to be calling the ng-submit method. Am I missing something?

  • Pingback: Forms/Validation Tuts | Pearltrees

  • Epsilon_Delta

    Many thanks, worked like charm. How can I include validation on input loosing focus in this directive? Started playing around with angular recently.

    • Andy Norborg

      Glad it worked for you! There are two ways I can interpret your question. One is whether there is a way to delay any given validation until after losing focus or the other is if you can have your own particular custom validator that doesn’t validate until after focus. Either of these can be pretty complicated depending on the results you want. I would argue this isn’t that different than asynchronous validation, i.e. using a service to validate a field. Basically you need to track the state of the validation. You want angular to see the field as invalid in some way until the validation is completed; in your case, after the input looses focus. We have a service example in this blog:

      http://code.realcrowd.com/using-mailguns-email-address-validation-service-with-angularjs/

      However, it may be a little to complex, but it has the different states I’m referring to. If you are doing a custom validation directive, you can just bind to the focus out of the element on the directives ‘link’ method (i.e. element.focusout(function() { … }).

      This is a tough question to fully answer in a comment. I’ll see if I can put a full blog together to go over this in detail. Until then, I hope the above gives you some useful guidance.

  • wcandillon

    Thank so much for this article. I was trying to build these custom directives myself and then found this article. You really nailed down out the issues.

    • Andy Norborg

      Thanks for the feedback! Glad you found the article useful!

  • Pingback: On the Bleeding Edge: Advanced AngularJS Form V...

  • Andy Norborg

    That’s a cool approach. We actually have evolved the controller a bit more to bring in some additional functionality and state (i.e. http://code.realcrowd.com/angularjs-automatically-disable-groups-of-elements-during-http-calls/), and have found it a helpful place to keep our custom state information. I think your filter is a neat approach as well if your trying to avoid the controller. Thanks!

  • Pingback: angular custom validation with business rules | My Blog

  • Jeff Dunlop

    Thank you for by far the best distillation of form validation into a declarative representation that I’ve found, and I got the basics working on one of my fields in short order. However, the verbosity of an angularised yet-another-bog-standard input field leaves much to be desired in comparison to mvc/jquery, and I’d like to see if it’s possible to reduce this by another level. If the typical validated field is to look like:

    Minimum Markup

    <span ng-show=”configurationMaintenance.minimumMarkup.$error.required”>Required</span>

    Would it be possible to write my own directive that takes a form name and a field name and perhaps a bit more, and reduce the boiler-plating aspect of this? In a rough sense, what would the markup that used such a directive look like? If I can see how it would be expressed in html, I’m pretty sure I can drill into my first custom directive to make it work.

    • Andy Norborg

      Thanks for the feedback Jeff. Its always difficult to balance reusability with conciseness. I thought about this a bit. There are a couple ways you can probably accomplish what you want. One is to use highly custom directives that can make assumptions about the markup (or generate it) based on your specific app. The other, which I’m leaning toward would be to create some sort of highly context-sensitive directive. The mark-up may look something like:

      Required

      The idea here is that the ‘rc-auto’ directive means different things on different elements. And that it is aware of all its ancestors, siblings, and children. So you would probably also need a provider that can track all of these directives and their relationships, and then make assumptions about their context. I think you could then also use the AngularJS ‘$compile’ service to dynamically apply directives. Notice in the above markup, I don’t have an ‘ng-model’ on the input. I’m assuming the ‘rc-auto’ directive on the form will find all of its child inputs and apply the ‘ng-model’ directive assuming a model name of ‘session’ and applying a property name of ‘username’ based on the name of the input. You have to be very careful when making all these assumptions of course.

      This is obviously a very high-level solution and not particularly well thought out. When getting into the nitty gritty things may get a little hairy. Its not a bad idea though. I’ll put it on my list of future directive ideas. If you have more specific questions as you implement your solution feel free to email me.

      • Jeff Dunlop

        Actually, after looking a bit more, maybe convention would work better than omniscience. The sticking point is in the input ng-class, and in the span ng-show. I’m going to take a crack at replacing the validation expression in ng-class with a simple rc-validation directive, and replace the ng-show with a rc-validation-message(“username”) directive.

  • SPD

    Good Post

  • David Quiros

    You’re code has saved me quite a bit of time and it’s a great example of the use of require in directives (which I hadn’t found a need for until now). I’d love to use your code in one of my projects, but … what’s the license? Thanks, Andy!

    • Andy Norborg

      Thanks David. It’s an MIT license and its located in the root of the github project (https://github.com/realcrowd/angularjs-utilities/blob/master/mit-license.txt). Your alternate approach would work, but even better, I can probably toss all the code in scope.$apply() block toward the bottom. I don’t think I need to check the phase since this is all happening in the submit event no scope should be being digested I believe. I’ll verify this and update the code it appropriate. Thanks again.

  • Harald Andertun

    Thank you, I use this in my project and it has saved me quite a bit of time. I have a question: can this be used with ng-form? It doesn’t work with ng-form out of the box.

    • Harald Andertun

      I want to use ng-form because I need to nest some forms. The outer form is a with rc-submit=”sendQuestionnaire()” while the inner is a . If the user tries to submit the outer form when not logged in the fields in the inner form needs attention. If the user tries to login in the inner form and makes a mistake, only these fields should be marked as has-error.

      • Andy Norborg

        Hi Harald. I’m glad you found this useful. Supporting ngForm is a bit tricky. AngularJS currently doesn’t support its own ng-submit with ngForm (https://github.com/angular/angular.js/issues/2513). As you can see ngForm does not have a submit event to listen for. I have a couple ideas for a solution:

        1) make rcSubmit nestable. This would involve adding an optional require to a _parent_ rcSubmit’ (require: [‘rcSubmit’, ‘?form’, ‘?^rcSubmit’]),. If their is a parent rcSubmit, instead of binding to the formElement submit event, add a handler to the rcParentSubmitController.onAttempt, other bind to the formElement submit. This would basically chain the attempts all the way to the top-level rcSubmit which still must use a regular form. The only reason I don’t like this approach is that it would be weird to nest rc-submits I think.

        2) make a new directive (or bring back) called rcAttempt. This directive would mimic rcSubmit except instead of looking for a form at its own level, it would look for an rcSubmit in the parent and bind to onAttempt.

        I would have to think these through some more before implementing this feature, but hopefully this give you some guidance if you wish to attempt you own directive for now. I hope the above is more helpful than confusing :-).

  • Huei Tan

    Hey, I create a new Module call `angular-validation`
    https://github.com/huei90/angular-validation

    I want to minimize the code of View for error/success message,
    But I pretty need someone to help me, welcome to join me =)

  • Martin

    There’s one thing I really don’t get. Within the required field you write ‘?form’. I assume the ? means it’s optional and that’s why you always check whether the controllers array has a second object.

    Within the post method you check whether a formController exists, take it or take null instead. A few lines later you check validity with “if (!formController.$valid) {” and here lies the problem. You can’t check $valid on null – it will give you a ReferenceError. It would be better to either make the form required, or add another parameter for validity-check method.

    • Andy Norborg

      Hi Martin, nice catch! Looks like a bug from combining the rc-attempt and rc-submit directive. form really should be required. I’ll correct this ASAP. Thanks!

  • Gabriel Briviesca

    Hi Andy, while using the rcSubmit I got into an issue when i used it in conjunction with the angular ui modal form. whenever i would execute $modalInstance dismiss or close methods, it would trigger the form submit event. Did you ever get this behavior? I ended up replacing the “submit” event listener in rcSubmit directive with a custom (eg “RC_SUBMIT”) one and that seems to be working now. Thanks for any feedback.

    • Sam Kenny

      Hi Gabriel, I have a similar issue – the submit event listener is being triggered when I perform other actions on my form. Can you elaborate on how to solved the issue.

      Thanks.

      • Gabriel Briviesca

        Hi Sam, The cause for my issue was that the button I was using to trigger the modal close didn’t have a type, and therefore the default (submit) was set. try giving it a type=”button” and that solved my problem. My solution before that, was to listen for a custom event (RC_SUBMIT) and then when it was triggered I would prevent default in it and then execute my code.

        • Sam Kenny

          Thanks. I ended up separating the real-time validation from the button click event – the form has a reasonable amount of data on it and so it works better this way. Thanks for your reply though.

  • Royce Lithgo

    On your register form as soon as you start typing the email address it goes to an error state and an invalid email address message shows. That’s not a very friendly UX.

    I do however like the approach presented here, but wouldn’t it be better to take it a step further and do all validations with directives? In other words, make the entire form-group div a directive including all the validation and set appropriate attributes for the validations required. I’m planing on doing just that as part of my form input directive.

    • Andy Norborg

      Hi Royce, I talked this through with our UX Designer, and while I agree an _error_ state might be inappropriate, some sort of state should draw attention to the field while it has focus. Waiting until the user leaves the field would require them to navigate back to the field if its not formatted correctly. One way to resolve this would be to have a slightly different style for invalid fields while focused. Perhaps, orange not red? but something to highlight the fact that the field is not complete while the fields has focus. Ideally you would probably like some additional state information about the validation workflow (i.e. attemptedCompletion, meaning the user has tried to leave the field). This requires creating your own validation workflow (I actually in the process of doing this, but it is not production ready). However the more custom you get, the less you are getting out of angular.

      I have considered doing the a directive on the form-group as well as an additional option. I didn’t want that to be the only route though because it makes things slightly less flexible. If I did create one though, I would probably create a service that both these existing directives could use as well as the form-group.

      Thanks for your feedback!

      • Royce Lithgo

        Thanks for the reply Andy. You’ve given me some ideas now. Namely that my validation core logic should be implemented as a service (which i’ll incorporate in to my input directive).

        Also about the email address validation, I totally get your point, but at the same time immediately displaying a warning / error after one key press is a little intimidating. How about waiting until blur and then set the focus back on the email field if an error is present? I’m not a huge fan of solutions that override focus though. It’s a trade off really. Or perhaps validate on a short timeout that gets reset after each keypress?

        • Andy Norborg

          Hi Royce. Glad I could help. You got me curious so I did some googling. I found the following article (http://alistapart.com/article/inline-validation-in-web-forms):

          “AFTER METHOD [on blur] HELPS USERS TO COMPLETE FORMS MORE QUICKLY”

          It makes some of your similar points on displaying the message too soon. I’m not a fan of manipulating the focus either. At any rate, you would have to have custom logic to track the _attempted completion_ state of the field. I have also being developing a “rcDraft” module to delay binding to the scope. Basically instead of binding directly to the scope, you are binding to a copy (draft) that is manually applied to the scope later. This would have the effect of delaying the validation. All that said and even with the referenced article, I would really like to figure out a UX mechanism to provide active validation feedback to the user without it being jarring.

          • Royce Lithgo

            Andy, that (after) validation solution presented in the link you posted is awesome (BTW, your link has some extra characters in it). As are the error icons / messages to the right of input. I’ve never been a big fan of errors appearing under fields and growing page length in the process.

            That is the solution I’ve been looking for. I will model my forms off that. Thanks for all your input!

  • markhuang

    I realized that the needsAttention function doesn’t validate when the input is a multi-select dropdown. Has anyone realized this?

    • Andy Norborg

      Hi Mark, I tested this by putting together a plunkr and it seems to be working as expected:

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

      Is this something specific you are seeing?

      • markhuang

        Well, I’m using AngularStrap and it didn’t work with its multi-select plugin. I checkout your Plunkr example and yeah it works brilliant. I’d need to go through AngularStrap’s implementation, it’s probably their fault then.

  • Yang Li

    Hi Andy! Awesome code, thank you for posting this. However, I have some issues with the implementation in firefox. Namely, the line

    fn(scope, {$event:event});

    does not work in firefox. If you try the code in firefox it will not bring up the popup because firefox cannot evaluate event.

    • Andy Norborg

      Hi Yang, thanks for the feedback! Sorry, I had an error in the code in the blog and on plunkr. The source on GitHub was correct. The problem was this line:

      formElement.bind(‘submit’, function (event) {

      I forgot to pass in the event parameter. I’ve updated the code on the blog and on plunkr. Thanks again for your input!

  • Guest

    Hey Andy,
    First of all – great work! so usaful!

    I have a question, i might be missing something.
    How can I validate each field by it content? (email validator, password validtor etc) and keep the css logic remain?

    Thanks

  • James North

    Andy, Just finished incorporating your rcSubmit directive into the “contact” form of my site, and it is working great! There is just one issue I am having trouble resolving. After a successful “submit”, my view controller resets the form fields (view model) to null. This clears the form fields, but does not reset the “attempted” flag maintained internally by your directive. Consequently all of the now blank required fields are decorated with error styling. Is there a way to reset the “attempted” flag in you directive from my view controller code?

    • Andy Norborg

      Hi James, glad you find the directive useful! Unfortunately I did not consider that use case. Have you attempted to set the property directly? (i.e. rc.loginForm.attempted = false). I’m trying to figure out how I would implement this. I was thinking maybe watching the $pristine property on the form maybe but technically a form is still $pristine it the user attempts a submit on an empty form. How does the ngFormController behave we you null out the view-model? are all the $dirty flags unset when you do that or do you manually call ngFormController.$setPristine()? Depending on how it behaves I could create something similar like rc.loginForm.clearAttempted() or something like that you can call as well.

      • James North

        Andy, as is often the case, after I left the comment, I engaged my brain and solved the problem. I figured out how to set the flag directly ( $scope.rc.{form name}.attempted = false). I am also nulling the view model and calling $scope.{for name}.setPristine(). This does the trick.

        Thanks for the prompt response.
        Jim

  • Prince Sharma

    great work bro. I just want to know how to do change in these directive for occur validation on blur event as well. Prince

    • http://programming-langugae.co.nr coach

      That i don’t know

  • Karthik Sankar

    This solution works the best. Thank you so much for sharing. I picked this approach against several other approaches like angular agility, angular-ui and another code available in plunker.

  • http://dandascalescu.com/ Dan Dascalescu

    This looks incredibly complicated. Is there a module we can use, along with an example, so as to focus on the business logic more than on good old form validation?

  • David T

    Why would we need required attribute in the input field ?

  • demisx

    Quite outdated by now. We are doing form validations in Angular differently nowadays.

    • Dhamotharan Sritharan

      Which validations you are using now?

      • demisx

        Validators pipeline and `ng-messages` for displaying validation messages.

        • Dhamotharan Sritharan

          Thank you. I would check them

        • Darren Sherwood

          I am also using ng-messages, but I am still using the rc-submit (slightly modified) I found here to stop submission. Do you still use it for that?

  • http://programming-langugae.co.nr coach

    will give you comment once I’m done reading this post!

  • tangerinewhite32

    have you guys thought of making an even more advanced tutorial with ng-messages?

  • ftf

    ff

  • Nelson Omuto

    try angular-ui-form-validation plugin

  • Claudia Gonzalez

    But what happens if there are two buttons- search and continue? For instance, I have a form that requires the user to input some info to conduct a search. When the user loads the page the “search” button is disabled and so should the “continue” button. When the user inputs data to search, then the search button is enabled. Once the users selects from the results, the “continue” button should switch from being disabled to enabled. Any ideas on how to manage that since the second button doesn’t have any inputs so i cant break it into two forms. thanks!

  • feezy23

    Real Crowd providing Real Value!

  • Nick Jordan

    just want to know, is there any way you can show angularfire, for an app that could use live sports feeds to create realtime, facebook posts, and in the least possible ammount of code.

    • Nick Jordan

      i have a goldmine of ideas, just need help.

      • Denis Poisson

        Hey, I’ve got this great idea for a 2000 page book about fish illustrations from the 19th century, but I don’t know anything about it. Can someone help? I saw ‘Angular’ and assumed it was related to angling? (ps: hope that wasn’t too harsh, just explaining why you haven’t had an answer ;0 but good luck!)