show validation error messages on submit in angularjs
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
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!
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 ;)