71
votes

I have a form which need to show validation error messages if clicked submit.

Here is a working plunker

 <form name="frmRegister" ng-submit="register();" novalidate>
      <div>
        <input placeholder="First Name" name="first_name" type="text" ng-model="user.firstName" required />
        <span ng-show="frmRegister.first_name.$dirty && frmRegister.first_name.$error.required">First Name is required</span>
      </div>
      <div>
        <input placeholder="Last Name" name="last_name" type="text" ng-model="user.lastName" required />
        <span ng-show="frmRegister.last_name.$dirty && frmRegister.last_name.$error.required">Last Name is required</span>
      </div>
      <div>
        <input placeholder="Email" name="email" type="email" ng-model="user.email" required />
        <span ng-show="frmRegister.email.$dirty && frmRegister.email.$error.required">Email is required.</span>
        <span ng-show="frmRegister.email.$dirty && frmRegister.email.$error.email">Invalid Email address.</span>
      </div>
      <input type="submit" value="Save" />
      <span ng-show="registered">YOU ARE NOW REGISTERED USER</span>
 </form>

Validation works normally when user start making changes. But it doesn't show any error messages If clicked submit without entering anything.

Any thought of achieving this?. Or in other way how can I make each input field $dirty when clicks the Submit button

13

13 Answers

105
votes

I found this fiddle http://jsfiddle.net/thomporter/ANxmv/2/ which does a nifty trick to cause control validation.

Basically it declares a scope member submitted and sets it true when you click submit. The model error binding use this extra expression to show the error message like

submitted && form.email.$error.required

UPDATE

As pointed out in @Hafez's comment (give him some upvotes!), the Angular 1.3+ solution is simply:

form.$submitted && form.email.$error.required
13
votes

Since I'm using Bootstrap 3, I use a directive: (see plunkr)

    var ValidSubmit = ['$parse', function ($parse) {
        return {
            compile: function compile(tElement, tAttrs, transclude) {
                return {
                    post: function postLink(scope, element, iAttrs, controller) {
                        var form = element.controller('form');
                        form.$submitted = false;
                        var fn = $parse(iAttrs.validSubmit);
                        element.on('submit', function(event) {
                            scope.$apply(function() {
                                element.addClass('ng-submitted');
                                form.$submitted = true;
                                if(form.$valid) {
                                    fn(scope, {$event:event});
                                }
                            });
                        });
                        scope.$watch(function() { return form.$valid}, function(isValid) {
                            if(form.$submitted == false) return;
                            if(isValid) {
                                element.removeClass('has-error').addClass('has-success');
                            } else {
                                element.removeClass('has-success');
                                element.addClass('has-error');
                            }
                        });
                    }
                }
            }
        }
    }]

    app.directive('validSubmit', ValidSubmit);

and then in my HTML:

<form class="form-horizontal" role="form" name="form" novalidate valid-submit="connect()">
  <div class="form-group">
    <div class="input-group col col-sm-11 col-sm-offset-1">
      <span class="input-group-addon input-large"><i class="glyphicon glyphicon-envelope"></i></span>
      <input class="input-large form-control" type="email" id="email" placeholder="Email" name="email" ng-model="email" required="required">
    </div>
    <p class="col-sm-offset-3 help-block error" ng-show="form.$submitted && form.email.$error.required">please enter your email</p>
    <p class="col-sm-offset-3 help-block error" ng-show="form.$submitted && form.email.$error.email">please enter a valid email</p>
  </div>
</form>

UPDATED

In my latest project, I use Ionic so I have the following, which automatically puts .valid or .invalid on the input-item's:

.directive('input', ['$timeout', function ($timeout) {
  function findParent(element, selector) {
    selector = selector || 'item';
    var parent = element.parent();
    while (parent && parent.length) {
      parent = angular.element(parent);
      if (parent.hasClass(selector)) {
        break;
      }
      parent = parent && parent.parent && parent.parent();
    }
    return parent;
  }

  return {
    restrict: 'E',
    require: ['?^ngModel', '^form'],
    priority: 1,
    link: function (scope, element, attrs, ctrls) {
      var ngModelCtrl = ctrls[0];
      var form = ctrls[1];

      if (!ngModelCtrl || form.$name !== 'form' || attrs.type === 'radio' || attrs.type === 'checkbox') {
        return;
      }

      function setValidClass() {
        var parent = findParent(element);
        if (parent && parent.toggleClass) {
          parent.addClass('validated');
          parent.toggleClass('valid', ngModelCtrl.$valid && (ngModelCtrl.$dirty || form.$submitted));
          parent.toggleClass('invalid', ngModelCtrl.$invalid && (ngModelCtrl.$dirty || form.$submitted));
          $timeout(angular.noop);
        }
      }

      scope.$watch(function () {
        return form.$submitted;
      }, function (b, a) {
        setValidClass();
      });


      var before = void 0;
      var update = function () {
        before = element.val().trim();
        ngModelCtrl.$setViewValue(before);
        ngModelCtrl.$render();
        setValidClass();
      };
      element
        .on('focus', function (e) {
          if (ngModelCtrl.$pristine) {
            element.removeClass('$blurred');
          }

        })
        .on('blur', function (e) {
          if (ngModelCtrl.$dirty) {
            setValidClass();
            element.addClass('$blurred');
          }
        }).on('change', function (e) {
          if (form.$submitted || element.hasClass('$blurred')) {
            setValidClass();
          }
        }).on('paste', function (e) {
          if (form.$submitted || element.hasClass('$blurred')) {
            setValidClass();
          }
        })
      ;

    }
  };
}])

and then in the HTML:

    <form name='form' novalidate="novalidate" ng-submit="auth.signin(form, vm)">
          <label class="item item-input item-floating-label">
            <span class="input-label">Email</span>
            <input type="email" placeholder="Email" ng-model="vm.email" autofocus="true" required
              >
          </label>
          <button ng-if="!posting" type="submit" class="item button-block item-balanced item-icon-right  call-to-action">Login<i class="icon ion-chevron-right"></i>
          </button>

and in the controller:

  self.signin = function (form, data) {
    if (!form.$valid) return;

    Authentication.emailLogin(data)
    //...

so, now, in the CSS, you can do stuff like:

.item.valid::before{
    float: right;
    font-family: "Ionicons";
    font-style: normal;
    font-weight: normal;
    font-variant: normal;
    text-transform: none;
    text-rendering: auto;
    line-height: 1;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    color: #66cc33;
    margin-right: 8px;
    font-size: 24px;
    content: "\f122";
}

.item.invalid::before{
    float: right;
    font-family: "Ionicons";
    font-style: normal;
    font-weight: normal;
    font-variant: normal;
    text-transform: none;
    text-rendering: auto;
    line-height: 1;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    color: #ef4e3a;
    margin-right: 8px;
    font-size: 24px;
    content: "\f12a";

/*
    border-left: solid 2px #ef4e3a !important;
    border-right: solid 2px #ef4e3a !important;
*/
}

MUCH SIMPLER!

5
votes

I also had the same issue, I solved the problem by adding a ng-submit which sets the variable submitted to true.

<form name="form" ng-submit="submitted = true" novalidate>
<div>
    <span ng-if="submitted && form.email.$error.email">invalid email address</span> 
    <span ng-if="submitted && form.email.$error.required">required</span>
    <label>email</label>
    <input type="email" name="email" ng-model="user.email" required>
</div>

<div>
    <span ng-if="submitted && form.name.$error.required">required</span>
    <label>name</label>
    <input type="text" name="name" ng-model="user.name" required>
</div>

<button ng-click="form.$valid && save(user)">Save</button>
</form>

I like the idea of using $submitted, I think I've to upgrade Angular to 1.3 ;)

3
votes

I can come up with 2 ways to achieve it.

The first one is to remove novalidate to enable the browser's validation.

Second, you can disable the save button when the form is not valid like this

<input ng-disabled="!frmRegister.$valid" type="submit" value="Save" />

Hope it helps.

3
votes

There are two simple & elegant ways to do it.

Pure CSS:

After first form submission, despite the form validity, Angular will add a ng-submitted class to all form elements inside the form just submitted.

We can use .ng-submitted to controller our element via CSS.

if you want to display an error text only when user have submitted e.g.

.error { display: none }
.ng-submitted .error {
     display: block;
}

Using a value from Scope:
After first form submission, despite the form validity, Angular will set [your form name].$submitted to true. Thus, we can use that value to control elements.

<div ng-show="yourFormName.$submitted">error message</div>
<form name="yourFormName"></form>
2
votes

My solution with bootstrap 3

http://jsfiddle.net/rimian/epxrbzn9/

<form class="form" name="form" ng-app novalidate>
  <div class="form-group">
    <input name="first_name"
          type="text"
          class="form-control"
          ng-model="first_name"
          placeholder="First Name"
          required />
  </div>
  <div class="form-group">
    <input name="last_name"
          type="text"
          class="form-control"
          ng-model="last_name"
          placeholder="Last Name"
          required />
  </div>

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

<div ng-show="submitted && form.$invalid" class="alert alert-danger">
  <div ng-show="form.first_name.$error.required">
    First Name is Required
  </div>
  <div ng-show="form.last_name.$error.required">
    Last Name is Required
  </div>
</div>

</form>
2
votes

You only need to check if the form is dirty and valid before submitting it. Checkout the following code.

<form name="frmRegister" data-ng-submit="frmRegister.$valid && frmRegister.$dirty ? register() : return false;" novalidate>      

And also you can disable your submit button with the following change:

<input type="submit" value="Save" data-ng-disable="frmRegister.$invalid || !frmRegister.$dirty" />

This should help for your initial

1
votes

http://jsfiddle.net/LRD5x/30/ A simple solution.

HTML

<form ng-submit="sendForm($event)" ng-class={submitted:submitted}>

JS

$scope.sendForm = function($event) {
  $event.preventDefault()
  $scope.submitted = true
};

CSS

.submitted input.ng-invalid:not(:focus) {
    background-color: #FA787E;
}

input.ng-invalid ~ .alert{
    display:none;
}
.submitted input.ng-invalid ~ .alert{
    display:block;
}
1
votes

I like the solution from realcrowd the best.

HTML:

<form role="form" id="form" name="form" autocomplete="off" novalidate rc-submit="signup()">
<div class="form-group" ng-class="{'has-error':  rc.form.hasError(form.firstName)}">
    <label for="firstName">Your First Name</label>
    <input type="text" id="firstName" name="firstName" class="form-control input-sm" placeholder="First Name" ng-maxlength="40" required="required" ng-model="owner.name.first"/>
    <div class="help-block" ng-show="rc.form.hasError(form.firstName)">{{rc.form.getErrMsg(form.firstName)}}</div>
</div>
</form>

javascript:

//define custom submit directive
var rcSubmitDirective = {
'rcSubmit': ['$parse', function ($parse) {
    return {
        restrict: 'A',
        require: ['rcSubmit', '?form'],
        controller: ['$scope', function ($scope) {
            this.attempted = false;

            var formController = null;

            this.setAttempted = function() {
                this.attempted = true;
            };

            this.setFormController = function(controller) {
              formController = controller;
            };

            this.hasError = function (fieldModelController) {
                if (!formController) return false;

                if (fieldModelController) {
                    return fieldModelController.$invalid && this.attempted;
                } else {
                    return formController && formController.$invalid && this.attempted;
                }
            };
            this.getErrMsg=function(ctrl){
                var e=ctrl.$error;
                var errMsg;
                if (e.required){
                    errMsg='Please enter a value';
                }
                return errMsg;
            }
        }],
        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;
                },
                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});
                        });
                    });
                }
          };
        }
    };
}]
};
app.directive(rcSubmitDirective);
0
votes
// This worked for me.
<form  name="myForm" class="css-form" novalidate ng-submit="Save(myForm.$invalid)">
<input type="text" name="uName" ng-model="User.Name" required/>
<span ng-show="User.submitted && myForm.uName.$error.required">Name is required.</span>
<input ng-click="User.submitted=true" ng-disabled="User.submitted && tForm.$invalid" type="submit" value="Save" />
</form>
// in controller
$scope.Save(invalid)
{
if(invalid) return;
// save form
}
0
votes

G45,

I faced same issue , i have created one directive , please check below hope it may be helpful

Directive :

    app.directive('formSubmitValidation', function () {

        return {
            require: 'form',
            compile: function (tElem, tAttr) {

                tElem.data('augmented', true);

                return function (scope, elem, attr, form) {
                    elem.on('submit', function ($event) {
                        scope.$broadcast('form:submit', form);

                        if (!form.$valid) {
                            $event.preventDefault();
                        }
                        scope.$apply(function () {
                            scope.submitted = true;
                        });


                    });
                }
            }
        };


  })

HTML :

<form  name="loginForm" class="c-form-login" action="" method="POST" novalidate="novalidate" form-submit-validation="">

<div class="form-group">
                                                <input type="email" class="form-control c-square c-theme input-lg" placeholder="Email" ng-model="_username" name="_username" required>
                                                <span class="glyphicon glyphicon-user form-control-feedback c-font-grey"></span>
                                                <span ng-show="submitted || loginForm._username.$dirty && loginForm._username.$invalid">
                                                    <span ng-show="loginForm._username.$invalid" class="error">Please enter a valid email.</span>
                                                </span>
                                            </div>
<button type="submit" class="pull-right btn btn-lg c-theme-btn c-btn-square c-btn-uppercase c-btn-bold">Login</button>
</form>
0
votes

A complete solution to the validate form with angularjs.

HTML is as follows.

<div ng-app="areaApp" ng-controller="addCtrler">
        <form class="form-horizontal" name="fareainfo">
            <div class="form-group">
                  <label for="input-areaname" class="col-sm-2 control-label">Area Name : </label>
                  <div class="col-sm-4">
                      <input type="text" class="form-control" name="name" id="input-areaname" ng-model="Area.Name" placeholder="" required>
                      <span class="text-danger" ng-show="(fareainfo.$submitted || fareainfo.name.$dirty) && fareainfo.name.$error.required"> Field is required</span>
                  </div>
            </div>
             <div class="col-sm-12">
                  <button type="button" class="btn btn-primary pull-right" ng-click="submitAreaInfo()">Submit</button>
             </div>
        </form>
    </div>

AngularJS App and Controller is as follows

var areaApp = angular.module('areaApp', []); 
areaApp.controller('addCtrler', function ($scope) {
    $scope.submitAreaInfo = function () {  
       if ($scope.fareainfo.$valid) {
         //after Form is Valid
       } else {
          $scope.fareainfo.$setSubmitted();
       }
    };
});

Important Code Segments

  1. ng-app="areaApp" ng-controller="addCtrler"
    Defines the angular app and controller

  2. ng-show="(fareainfo.$submitted || fareainfo.name.$dirty) && fareainfo.name.$error.required"
    Above condition ensure that whenever a user first sees the form there's no any validation error on the screen and after a user does changes to the form it ensure that validation message show on the screen. .name. is the name attribute of the input element.

  3. $scope.fareainfo.$valid
    Above code, segment check whether the form is valid whenever a user submits the form.

  4. $scope.fareainfo.$setSubmitted();
    Above code, segment ensures that all validation messages are displayed on the screen whenever a user submits the form without doing anything.

-3
votes

Try this code:

<INPUT TYPE="submit" VALUE="Save" onClick="validateTester()">

This funvtion will validate your result

function validateTester() {
    var flag = true
    var Tester = document.forms.Tester
    if (Tester.line1.value!="JavaScript") {
        alert("First box must say 'JavaScript'!")
        flag = false
        }
    if (Tester.line2.value!="Kit") {
        alert("Second box must say 'Kit'!")
        flag = false
        }
    if (flag) {
        alert("Form is valid! Submitting form...")
        document.forms.Tester.submit()
        }
    }