5

I've hacked a solution to my problem, but I am not pleased, it doesn't feel "angular" to me, and it adds to the maintenance of the code.

First, the form requirements:

  1. Modular Directives (so I can re-use them.)
  2. Each field can have multiple errors, but only one is shown at a time, in a logical order...
  3. Hide the submit button until the form is complete, not just valid.
  4. Bootstrap Themed
  5. Do it "the angular way."

The problem is #3. I tried this:

<div ng-show="myform.$valid">
   <input type="submit" value="Submit" />
</div>

But this just makes the submit button show up as soon as one field is valid, and then it hides again as you start the next field. My "hack fix" was to create variables on the scope and a method in the controller to check them all (mainly to keep the view clean...) But this just doesn't feel right.

Here's my fiddle: http://jsfiddle.net/thomporter/e3jye/

Thom Porter
  • 2,604
  • 2
  • 19
  • 23

3 Answers3

13

I was just poking around with this and here's what I landed on for highlighting errors on an email field in logical order (using bootstrap form control groups)... http://jsfiddle.net/kNKbJ/1/

<form name="form" ng-app>
    <div class="control-group" ng-class="{true: 'error'}[form.email.$error.email || (submitted && form.email.$error.required)]">
        <label class="control-label" for="email">Your email address</label>
        <div class="controls">
            <input type="email" name="email" ng-model="email" required />
            <span class="help-inline" ng-show="submitted && form.email.$error.required">Required</span>
            <span class="help-inline" ng-show="form.email.$error.email">Invalid email</span>
        </div>
    </div>

    <button type="submit" class="btn btn-primary btn-large" ng-click="submitted=true">Submit</button>
</form>

You could add the submitted check to anything you don't want to show until after first submit

Paul D. Waite
  • 96,640
  • 56
  • 199
  • 270
JeremyWeir
  • 24,118
  • 10
  • 92
  • 107
  • I love the "submitted" idea... That's one thing I don't like about angular is it's preemptive errors... I would much rather the validation trigger on blur, instead of keyup.... Your submitted idea may be the next best thing! I simplified it a bit, taking from your last idea on adding the submitted check to hold errors until submit - makes it a bit cleaner in the code (another complaint of mine.) Also, for the control-group div class you can just check form.email.$invalid - it is insignificant why at that juncture... http://jsfiddle.net/thomporter/ANxmv/2/ – Thom Porter Apr 05 '13 at 20:54
  • Yeah I mentioned it could be used on anything, but I left it that way to demonstrate checking some things before submit and some things after. – JeremyWeir Apr 05 '13 at 21:42
  • This really is the coolest solution I've seen so far, and as such, I've changed my check mark! Sorry @blesh! – Thom Porter Apr 05 '13 at 23:03
8

It's a common problem that people don't want all of their validation messages showing at the same time, even if they all apply.

The following form should fit your requirements, however I didn't "Bootstrap theme" the error validation messages. It is only using the default angular functionality.

But in a nutshell, it should show the required message, if a value exists but it's not a valid email, it should show the invalid email message.

It also hides the submit button if it's not valid... HOWEVER... I recommend just disabling the submit button with ng-disabled instead. It's poor usability IMO to not let people know where the submit button is.

<form name="myForm">
   <label for="email">Email</label>
   <input type="email" id="email" name="email" ng-model="formData.email" required/>
   <span ng-show="myForm.email.$error.required && myForm.email.$dirty">required</span>
   <span ng-show="!myForm.email.$error.required && myForm.email.$error.email && myForm.email.$dirty">invalid email</span>

   <button type="submit" class="btn" ng-show="myForm.$valid">Submit</button>
</form>

It gets a little long adding those checks to the validation, but it is the "angular way" to do it.

I hope that helps.

Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
  • I don't understand how I had to control the button before, but now, using your method ("the angular way"), I don't. I forked my fiddle, and completely re-did the validation, using ng-minlength/maxlength and ng-pattern. I came up with a new problem regarding ng-pattern. The pattern itself seems to work, but it shows the pattern error when it should show the min-length error. If however you type in a short-pattern-resolving password, you get min-length error, lilke "a3A"... Any ideas? http://jsfiddle.net/thomporter/UZrex/1/ – Thom Porter Jan 27 '13 at 05:41
  • Sure, one thing you can do to help debug validation is write out the $error: `{{myForm.field.$error | json}}`. In this case you'll see that minlength and maxlength are actually false. This is because the pattern $parser is actually run before the others and the others don't get a chance to process. If you look at the [input directive's code on GitHub](https://github.com/angular/angular.js/blob/master/src/ng/directive/input.js) you can actually see how they're pushed on the $parser stack. It actually seems like a bug to me that all validators wouldn't be run, but perhaps it was by design, dunno – Ben Lesh Jan 28 '13 at 03:04
  • Wow, nice. I moved the ng-pattern to before the min/max length and it works as expected. I'd call it a feature (if you know about it! :) – Thom Porter Jan 29 '13 at 03:31
  • So if you wanted to add a class to the label if the form field has one of the errors, what would be the "angular way"? I can't imagine it would be `` – JeremyWeir Apr 05 '13 at 19:23
  • Not quite, it would be: ``, where `.error-class` is the class you're adding ... but then again, that would be a very silly thing to do unless you were assigning a different class for each invalid state. Really, you'd probably be fine just checking `$dirty` and `$invalid`, since you're not really displaying a message with the CSS class change. – Ben Lesh Apr 08 '13 at 13:47
3

I wanted to add another solution to this and make it as complete as possible. This validation has a similar look & feel to asp.net's validation summary control.

The form loads from JSON. The form fields and validation are pulled from a function called requiredFields. Validation doesn't show up until you've clicked the button, instead of being in your face on load.

Here's a bit of the relevant code:

HTML

<div ng-show="showValidation() &&  !formReset">
    <h4>The following errors were found:</h4>
    <ul ng-repeat="(key,val) in requiredFields()">
        <li ng-show="myForm[key].$invalid">{{val}} is required</li>
    </ul>
</div>

<form name="myForm" novalidate>
  <label>Age: <span class="ng-cloak" ng-show="myForm.age.$valid">&#10004;</span></label>
  <input type="number" name="age" ng-model="form.age" required />
  <button ng-click="clickButton()">Submit</button>
</form>

JS

$scope.clickButton = function() {
  $scope.formReset = false;
  $scope.showValidation = function () { return $scope.myForm.$invalid; }
  if ($scope.myForm.$valid)
    $scope.jsonoutput = JSON.stringify($scope.form);
}

Full code: http://plnkr.co/edit/GAHHab7pEFtlyHnPWVux?p=preview


Side note: "novalidate" in the form tag is important if you don't want the pop up tooltip showing up. From my understanding, the 'required' attribute is part of HTML5 and is conflicting with angularjs' required directive.
mnsr
  • 12,337
  • 4
  • 53
  • 79
  • Very interesting... What makes this specific to ASP.net devs? – Thom Porter Apr 27 '13 at 14:57
  • It's not specific to them. What I meant was, the validation look & feel for the end user that i used, is similar to the 'validation summary' control that asp.net web forms and mvc come with. e.g. I had an old asp.net form which i wanted to spice up with angular, and i wanted to keep the look and feel the same so i used a similar style to the above. I re-worded that bit to prevent any more confusion :) – mnsr Apr 28 '13 at 00:12
  • This is a nice solution until Angular can provide some sort of built-in summary control and friendly names for controls. – Tyler Forsythe Jun 27 '13 at 17:38