How to validate forms with dynamic ngMessages - angularjs

This plunk has a form with a field that only allows to enter aaa. Note that the error message is set in the controller, not in the html. When the user clicks on Submit they should see the message, but the message is not shown. What's wrong with this code?
HTML
<body ng-app="ngMessagesExample" ng-controller="ctl">
<form name="myForm" novalidate ng-submit="submitForm()">
<label>
This field is only valid when 'aaa' is
<input type="field1"
ng-model="data.field1"
name="field1"
required />
</label>
<div ng-messages="myForm.field1.$error" style="color:red">
<div ng-message-exp="required">{{errorMsg}}</div>
</div>
<br/><br/>
<button style="float:left" type="submit">Submit</button>
</form>
</body>
Javascript
var app = angular.module('ngMessagesExample', ['ngMessages']);
app.controller('ctl', function ($scope) {
$scope.submitForm = function() {
if ($scope.field1 != 'aaa')
$errorMsg = "This field should be 'aaa'";
else
$errorMsg = "";
};
});

Forget my previous answer.
Easiest and most robust is actually to make a new directive.
var app = angular.module('ngMessagesExample', ['ngMessages']);
app.directive("aaa", function() {
return {
restrict: "A",
require: "ngModel",
link: function(scope, element, attributes, ngModel) {
ngModel.$validators.aaa = function(modelValue) {
return modelValue === 'aaa';
}
}
};
});
And your controller:
app.controller('ctl', function ($scope) {
$scope.data = {
field1: ""
}
$scope.submitForm = function(){
//extra whatever code
}
});
Your HTML should be this:
<body ng-app="ngMessagesExample" ng-controller="ctl">
<form name="myForm" novalidate ng-submit="submitForm(myForm)">
<label>This field is only valid when 'aaa' is</label>
<input type="field1"
ng-model="data.field1"
name="field1"
required aaa/>
<div ng-messages="myForm.field1.$error" style="color:red">
<div ng-message="required">FIELD IS REQUIRED!!</div>
<div ng-message="aaa">FIELD MUST BE 'aaa'</div>
</div>
<button style="float:left" type="submit">Submit</button>
</form>
</body>

Related

scope.$eval() is undefined if both input fields have the same directive

I'm trying to validate two password input fields. Simply confirm that they are equal. (Suggest another approach if mine is way wrong)
I have implemented a directive with a simple validation that checks if the "confirm" password is the same as the original. But the directive also checks for other things, so I need to have both input fields to have it.
The problem is that when I have my directive on both input fields, I cannot read their model values through the attribute (to check if they match).
Here is a working demo without the directive on the first password:
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
});
app.directive('myDir', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
ctrl.$validators.mismatch = function(modelValue, viewValue) {
// MAIN CODE:
return viewValue === scope.$eval(attrs.confirm);
};
ctrl.$validators.short = function(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
return true;
}
if (modelValue.length >= 3) {
return true;
}
return false;
}
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">
<form name="form1">
<input type="password" name="password1" ng-model="pass1"><br>
<input type="password" my-dir confirm="pass1" name="password2" ng-model="pass2"><br>
<pre>{{form1.password2.$error | json}}</pre>
<p ng-show="form1.password2.$error.mismatch" style="color:red">Passwords are different</p>
</form>
</div>
If I change the first filed to:
<input type="password" my-dir confirm="pass2" name="password1" ng-model="pass1">
to validate in both directions, then scope.$eval(attrs.confirm) becomes undefined for both fields.
Here is a demo of my issue:
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
});
app.directive('myDir', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
ctrl.$validators.mismatch = function(modelValue, viewValue) {
// `scope.$eval(attrs.confirm)` always undefined
return viewValue === scope.$eval(attrs.confirm);
};
ctrl.$validators.short = function(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
return true;
}
if (modelValue.length >= 3) {
return true;
}
return false;
}
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">
<form name="form1">
<input type="password" my-dir confirm="pass2" name="password1" ng-model="pass1"><br>
<input type="password" my-dir confirm="pass1" name="password2" ng-model="pass2"><br>
<pre>{{form1.password2.$error | json}}</pre>
<p ng-show="form1.password2.$error.mismatch || form1.password.$error.mismatch" style="color:red">
Passwords are different
</p>
</form>
</div>
You need to do 2 things:
1. Add ng-model-options="{allowInvalid: true}" so invalid value will still update scope value.
2. Now you have problem that e.g. changing 2nd input wont trigger 1st re-validation. This is done using observe:
<body ng-controller="MainCtrl" ng-init="x = 0; y = 0">
<form name="form1">
<input type="password" my-dir="{{y}}" confirm="pass2" name="password1" ng-model="pass1" ng-model-options="{allowInvalid: true}"
ng-change="x = x + 1"><br>
<input type="password" my-dir="{{x}}" confirm="pass1" name="password2" ng-model="pass2" ng-model-options="{allowInvalid: true}"
ng-change="y = y + 1"><br>
and
attrs.$observe('myDir', function() {
ctrl.$validate();
});
http://plnkr.co/edit/ws4tVWGXfFNR2yqLRJN7?p=preview
P.S. for usual fields I would write my-dir="{{pass1}}" and then no need in $eval and ng-change, but for passwords... not sure

AngularJS: Hiding ng-message until hitting the form-submit button

This is a typical example of the use of ng-messages in AngularJS (1.x):
<form name="demoForm">
<input name="amount" type="number" ng-model="amount" max="100" required>
<div ng-messages="demoForm.amount.$error">
<div ng-message="required">This field is required</div>
</div>
<button type="submit">test submit</button>
</form>
see: http://jsfiddle.net/11en8swy/3/
I now want to change this example so the "This field is required" error only shows when the field is touched ($touched) or the user hits the submit button.
I cannot use the ng-submitted class on the form since the validation error prevents the submitting of the form.
How should I do this?
Thanks
You can do this using ng-show:
<div ng-messages="demoForm.amount.$error" ng-show="demoForm.amount.$touched">
<div ng-message="required">This field is required</div>
</div>
And use a custom directive. See a working demo:
var app = angular.module('app', ['ngMessages']);
app.controller('mainCtrl', function($scope) {
});
app.directive('hasFocus', function($timeout) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attr, ctrl) {
element.on('focus', function() {
$timeout(function() {
ctrl.hasFocusFoo = true;
})
});
element.on('blur', function() {
$timeout(function() {
ctrl.hasFocusFoo = false;
})
});
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular-messages.js"></script>
<body ng-app="app" ng-controller="mainCtrl">
<form name="demoForm">
<input name="amount" type="number" ng-model="amount" max="100" required has-focus>
<div ng-messages="demoForm.amount.$error" ng-show="demoForm.amount.$touched || demoForm.amount.hasFocusFoo">
<div ng-message="required">This field is required</div>
</div>
<button type="submit">test submit</button>
</form>
</body>
The directive is basically setting another hasFocusFoo field on the ngModel controller then we can easily use that directive.
Ah, at the PC at last.
https://plnkr.co/edit/EX3UmoAOKmTKlameBXRa?p=preview
<form name="mc.form">
<input type="text" name="empty" ng-model="mc.empty" required />
<label ng-show="mc.form.empty.$dirty && mc.form.empty.$error.required">i'm empty</label>
</form>
MainController.$inject = ['$timeout'];
function MainController($timeout) {
var vm = this;
$timeout(function(){
vm.form.$setPristine();
});
vm.submit = function(){
if(vm.form.$valid){
alert('yay');
}else{
(vm.form.$error.required || []).forEach(function(f){
f.$dirty = true;
});
}
}
}
Here is how I handle this task in my solution. form.$setPristine() - sets the field in a pristine state, so field isn't $dirty and error hidden. But after submit I manually state required fields in a $dirty state, so errors become visible. + if you type something, and delete it after, the error would be visible without submitting a form.

AngularJS - write custom directive to validate passed values

I have an html form that looks like this :
<div class="row col-lg-offset-3">
<div class="form-group col-lg-6" ng-class="{ 'has-error': userForm.Age.$invalid && userForm.Age.$dirty}" show-errors >
<label class="control-label">Age</label>
<input type="text" class="form-control" name="Age" ng-model="user.Age" ng-required='!user.phonenumber' placeholder="Age"/>
</div>
</div>
Directive:
(function(){
angular.module('myApp', [])
.controller('studentDataController', function($scope) {})
.directive('showErrors', function() {
return {
restrict: 'A',
require: '^form',
link: function (scope, el, attrs, formCtrl) {
var inputEl = el[0].querySelector("[Age]");
var inputNgEl = angular.element(inputEl);
var inputValue = inputNgEl.attr('Age');
var isValid = (inputValue >= 3 && inputValue < 100);
inputNgEl.bind('blur', function() {
el.toggleClass('has-error', isValid);
})
}
}
});
})();
I am trying to validate input for Age field when it blurs out.Age value should be between 3 to 99.i.i.e check if the value is valid or invalid when user is done typing and leaves the text field.Then if the value is invalid, apply the has- class
The directive though is not working. Did I miss anything ?
If really have to do that via custom directive please see below:
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
});
app
.directive('ngAge', NgLength);
function NgLength() {
return {
restrict: 'A',
require: 'ngModel',
link: function($scope, $element, $attrs, ngModel) {
$scope.$watch($attrs.ngModel, function(value) {
var isValid = (value > 3 && value < 100);
ngModel.$setValidity($attrs.ngModel, isValid);
});
}
}
}
/* Put your css in here */
.has-error {
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="app">
<form name="userForm" ng-class="{ 'has-error': userForm.Age.$invalid && userForm.Age.$dirty}">
<label class="control-label">Age</label>
<input type="text" class="form-control" name="Age" ng-model="user.Age" ng-age placeholder="Age" />
</form>
</body>
AngularJS 1.3.x introduces $validators pipeline - it is much simpler to write custom validation rules with them.
A collection of validators that are applied whenever the model value changes. The key value within the object refers to the name of the validator while the function refers to the validation operation. The validation operation is provided with the model value as an argument and must return a true or false value depending on the response of that validation.
var app = angular.module('app', []);
app
.controller('MainCtrl', function($scope) {
$scope.name = 'World';
}).directive('ngAge', function NgLength() {
return {
restrict: 'A',
require: 'ngModel',
link: function($scope, $element, $attrs, ngModel) {
ngModel.$validators.validAge = function(modelValue, viewValue) {
var age = modelValue || viewValue;
return age > 3 && age < 100
};
}
}
});
.has-error {
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.min.js"></script>
<body ng-app="app">
<form name="userForm" ng-class="{ 'has-error': userForm.Age.$invalid && userForm.Age.$dirty}">
<label class="control-label">Age</label>
<input type="text" class="form-control" name="Age" ng-model="user.Age" ng-age placeholder="Age" />
</form>
</body>
You can use max, min directive. Please sample below
var app = angular.module('plunker', []);
.has-error {
background-color:red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link data-require="bootstrap-css#*" data-semver="3.3.1" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
<html ng-app="plunker">
<body>
<div class="row col-lg-offset-3">
<form name="userForm" class="form-group col-lg-6" ng-class="{ 'has-error': userForm.Age.$invalid && userForm.Age.$dirty}" show-errors>
<label class="control-label">Age</label>
<input type="number" class="form-control" name="Age" ng-model="user.Age" ng-required='!user.phonenumber' placeholder="Age" max="100" min="3" />
</form>
</div>

Reset Validity of ngModel

Recently, I had a problem with Angular form validity. I easy can to add the entry to Array with help ngModel.$setValidity, but I can't to remove it after. My html tag has a lot of valid/invalid classes. I'd tried to do the form to pristine. But it does't work. How that things to do generally? Help me please! (Sorry for my english =) if I've made a mistake somewhere.)
It's not terribly well documented, but you can actually pass in null to the $setValidity() function in order to completely clear a validation flag.
If you want to set it to be valid then simply pass in true
//Reset validity for this control
this.form.firstName.$setValidity('someValidator', null);
//Or set to valid
this.form.firstName.$setValidity('someValidator', true);
And here is a running snippet to demonstrate this technique.
(function() {
'use strict';
function MainCtrl() {
this.firstName = 'Josh';
}
MainCtrl.prototype = {
setInvalid: function(ctrl) {
ctrl.$setValidity('customValidator', false);
},
setPristine: function(ctrl) {
ctrl.$setValidity('customValidator', null);
},
};
angular.module('sample', [])
.controller('MainCtrl', MainCtrl);
}());
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" />
<div class="container" ng-app="sample" ng-controller="MainCtrl as ctrl">
<div class="row">
<div class="col-sm-12">
<form name="ctrl.form">
<div class="form-group" ng-class="{'has-error':ctrl.form.firstName.$invalid}">
<label class="control-label">First Name</label>
<input type="text" class="form-control" name="firstName" ng-model="ctrl.firstName" />
</div>
<button type="button" class="btn btn-danger" ng-click="ctrl.setInvalid(ctrl.form.firstName)">Set Invalid</button>
<button type="button" class="btn btn-success" ng-click="ctrl.setPristine(ctrl.form.firstName)">Set Valid</button>
</form>
</div>
</div>
</div>
(function () {
angular.module("App")
.directive("password", password);
function password() {
var lastTrueRegex = {};
var regexes = {
week: /(?=^.{8,}$).*$/,
pettyWeek: /(?=^.{8,}$)(?=.*\d).*$/,
normal: /(?=^.{8,}$)(?=.*\d)(?=.*[a-z]).*$/,
prettyStrong: /(?=^.{8,}$)(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*$/,
strong: /(?=^.{8,}$)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?![.\n]).*$/,
veryStrong: /(?=^.{8,}$)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?![.\n])(?=.*[!##$%^&*]+).*$/
};
function forEach(data, callback) {
for (var key in data) {
if (data.hasOwnProperty(key)) {
callback(key, data[key]);
}
}
}
return {
require: "ngModel",
restrict: 'EA',
link: function (scope, element, attributes, ngModel) {
ngModel.$parsers.unshift(function (textValue) {
forEach(regexes, function (key, regex) {
if (regex.test(textValue)){
lastTrueRegex.name = key;
lastTrueRegex.value = true;
}else{
ngModel.$setValidity(key, null);
}
});
if (lastTrueRegex.name){
ngModel.$setValidity(lastTrueRegex.name, lastTrueRegex.value);
return textValue;
}
});
ngModel.$formatters.unshift(function (modelValue) {
//ngModel.$setValidity('strongPass', isValid(modelValue));
return modelValue;
});
}
};
}
})();
<form name="myForm">
<input type="text" name="password" ng-model="textValue" password/>
</form>

Angular setValidity not working?

I first using setValidity to make a directive in Angular.but not as my expected,here is my code:
angular.module('myApp', [])
.controller('ctrl',function($scope){
$scope.pw='';
})
.directive('pwCheck', function(){
return {
require: 'ngModel',
link: function (scope, elem, attrs, ctrl) {
elem.on('keyup', function () {
scope.$apply(function () {
var len = elem.val().length;
if(len===0){
ctrl.$setValidity('zero',true);
} else if(len>1 && len<6){
ctrl.$setValidity('one',true);
} else {
ctrl.$setValidity('two',true);
}
});
});
}
};
});
HTML:
<body ng-controller="ctrl">
<form id="myForm" name="myForm">
<input type="text" ng-model="pw" pw-check />
{{myForm.$error}}
<div class="msg-block" ng-show="myForm.$error">
<span class="msg-error" ng-show="myForm.pw.$error.zero">
Input a password.
</span>
<span class="msg-error" ng-show="myForm.pw.$error.one">
Passwords too short.
</span>
<span class="msg-error" ng-show="myForm.pw.$error.two">
Great.
</span>
</div>
</form>
</body>
Online Demo:
http://jsbin.com/cefecicu/1/edit
I think you need:
//Reset your validity
ctrl.$setValidity('zero',true);
ctrl.$setValidity('one',true);
ctrl.$setValidity('two',true);
if(len===0){
ctrl.$setValidity('zero',false);
} else if(len>=1 && len<6){ //use len>=1 instead
ctrl.$setValidity('one',false);
} else {
ctrl.$setValidity('two',false);
}
Using false to indicate errors (not valid):
And give a name to your input:
<input type="text" ng-model="pw" name="pw" pw-check />
http://jsbin.com/cefecicu/11/edit

Resources