Angular setValidity not working? - angularjs

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

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 - input type="number" not clearable for non-number (NaN)

Clear number input type does not work for 'e' number
When I clear the input field with input eee in number type, it does not get cleared. Any other input numbers get cleared. Check the JSFiddle. Any hints would be appreciated.
http://jsfiddle.net/2ankx9up/
<div ng-app="app">
<div ng-controller="MainCtrl">
<input type="number" class="form-control" data-ng-model="searchAll">
</input>
<a class="clear" href="" data-ng-click="clearSearch()">X</a>
</div>
</div>
var app = angular.module("app", []);
app.controller("MainCtrl", function ($scope) {
$scope.searchAll = "";
$scope.clearSearch = function () {
$scope.searchAll = "";
};
});
The ng-model directive is unable to clear the content of an <input type="number"> element when that content parses to NaN (not a number). This can happen when a user pastes invalid content or simply types "eee".
One fix is to add a custom directive:
app.directive('clearNan', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
ngModel.$formatters.push(function(value) {
if (!value) elem.val(null);
return value;
});
}
};
})
Usage:
<input type="number" clear-nan ng-model="x" />
The DEMO
angular.module('numfmt-error-module', [])
.directive('clearNan', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
ngModel.$formatters.push(function(value) {
if (!value) elem.val(null);
return value;
});
}
};
})
.run(function($rootScope) {
$rootScope.typeOf = function(value) {
return typeof value;
};
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="numfmt-error-module">
<input clear-nan type="number" ng-model="x" />
<br>
{{ x }} : {{ typeOf(x) }}
<br>
<button ng-click="x=''">Clear input</button>
</body>

AngularJS datetimepicker directive not work when change value

I have a problem with datetimepicker in AngularJS. When the page loaded, datetimepicker directive run, and I got the right value I want. But when I chose another date, directive does not work, although I have to change the event inside.
A few days ago, It worked, but not now. I tested many times. I don't know why, because I didn't change anything.
The code:
.directive("datetimeselect", [
"Config", function (Config) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, el, attr, ngModel) {
$(el).datetimepicker({
format: Config.defaultConfig.dateTimeFormat
});
el.on('dp.change', function (event) {
scope.$apply(function () {
var date = moment(event.date);
ngModel.$setViewValue(date.format(Config.defaultConfig.dateTimeFormat));
});
});
//format text from the user (view to model)
ngModel.$parsers.push(function (data) {
return moment(data).format(Config.defaultConfig.dateTimeFormat);
});
//format text going to user (model to view)
ngModel.$formatters.push(function (data) {
return moment(data).format(Config.defaultConfig.dateTimeFormat);
});
}
};
}
])
And this is HTML
<div class="form-group col-md-6">
<label for="ToTime" class="control-label">To Time</label>
<input type="text" class="form-control" name="ToTime" id="ToTime"
ng-model="record.ToTime" datetimeselect />
</div>
The $parser is unnecessary and the $formatter needs to set the date:
function postLink(scope, element, attr, ngModel) {
var ignoreChangeEvent = false;
element.datetimepicker();
element.on('dp.change', function(event) {
if (ignoreChangeEvent) {
ignoreChangeEvent = false;
return;
}
scope.$apply(function() {
ngModel.$setViewValue(event.date);
});
});
ngModel.$formatters.push(function (date) {
ignoreChangeEvent = true;
element.data("DateTimePicker").setDate(date);
});
}
The DEMO
angular.module('bootstrap-timepicker', [])
.directive('datetimepicker', [
function() {
return {
restrict: 'A',
link: postLink,
require: 'ngModel'
};
function postLink(scope, element, attr, ngModel) {
var ignoreChangeEvent = false;
element.datetimepicker();
element.on('dp.change', function(event) {
if (ignoreChangeEvent) {
ignoreChangeEvent = false;
return;
}
scope.$apply(function() {
ngModel.$setViewValue(event.date);
});
});
ngModel.$formatters.push(function (date) {
ignoreChangeEvent = true;
element.data("DateTimePicker").setDate(date);
});
}
}
])
.controller('IndexController', function($scope) {
$scope.date = new Date();
});
<link rel="stylesheet" href="//unpkg.com/bootstrap#3/dist/css/bootstrap.css">
<link rel="stylesheet" href="//unpkg.com/bootstrap#3/dist/css/bootstrap-theme.css">
<link rel="stylesheet" href="//unpkg.com/bootstrap-datetime-picker#2.3/css/bootstrap-datetimepicker.css">
<script src="//unpkg.com/jquery#2"></script>
<script src="//unpkg.com/bootstrap#3/dist/js/bootstrap.js"></script>
<script src="//unpkg.com/moment"></script>
<script src="//unpkg.com/eonasdan-bootstrap-datetimepicker#3/src/js/bootstrap-datetimepicker.js"></script>
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="bootstrap-timepicker">
<div class="container" ng-controller="IndexController">
<h4>Datetimepicker</h4>
<div class="form-group">
<div class='input-group date' datetimepicker ng-model="date">
<input type='text' class="form-control" />
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
<p>
ng-model value: {{date}}
</p>
<div class='input-group date' datetimepicker ng-model="date">
<input type='text' class="form-control" />
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</div>
</div>
</body>

Communication between directive and controller angularjs

I would like to know if it is possible to send the user some kind of error message when the email already exist.
I have some directive like this. :
(function() {
angular
.module('app')
.directive('emailNotUsed',emailNotUsed);
emailNotUsed.$inject = ['$http', '$q'];
function emailNotUsed ($http, $q) {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$asyncValidators.emailNotUsed = function(modelValue, viewValue) {
return $http.post('/email',viewValue).then(function(response) {
console.log(response);
return response.data == true ? /*$q.reject(response.data.errorMessage)*/ console.log("Show Error Message"): true;
// console.log(response);
});
};
}
};
}
}());
As you can see this returns Show error in the console. But is it possible to pass this value to the register controller ? And set the value to true or false ?
(function () {
'use strict';
angular
.module('app')
.controller('RegisterController', RegisterController);
RegisterController.$inject = ['UserService', '$http','$q' ];
function RegisterController(UserService, $http,$q) {
var vm = this;
vm.register = register;
function register() {
vm.dataLoading = true;
UserService.Create(vm.user)
}
}
})();
Or could i give an error messages directly into the html.
<div class="container" ng-controller="RegisterController as vm">
<div class="col-md-6 col-md-offset-3">
<div ng-show="vm.error" class="alert alert-danger">{{vm.error}}</div>
<form name="form" ng-submit="!form.$pending && form.$valid && vm.register()" role="form">
<div>
<label for="email">Email</label>
<input type="text" name="email" id="email" class="form-control" ng-model="vm.user.email" email-not-used ng-model-options="{ debounce: 500 }" required >
</div>
<div class="form-actions">
<button type="submit" ng-disabled="form.$invalid || vm.dataLoading" class="btn btn-primary">Register</button>
Cancel
</div>
</form>
</div>
</div>
My solution
After alot of struggling today i found a solution for my problem.
I modified the emailNotUsed directive to emailValidator.
(function() {
angular
.module('app')
.directive('emailValidator',emailValidator);
emailValidator.$inject = ['$q', '$timeout','$http'];
function emailValidator ($q, $timeout,$http){
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$asyncValidators.email = function(modelValue, viewValue) {
var deferred = $q.defer();
DoesEmailExist()
function DoesEmailExist () {
$http.post('/email',viewValue).then(function(response) {
if (response.data==true) {
console.log('Email does exist')
deferred.reject();
}else {
deferred.resolve();
}
});
};
console.log(deferred.promise)
return deferred.promise;
};
}
}
}
}());
And in registration.html i added this :
<div class="form-group" ng-class="{ 'has-error': form.email.$dirty && form.email.$invalid }">
<label for="email"> Email</label>
<input type="text" ng-model="signup.email" ng-model-options="{ debounce: 500 }" name="email" id="email" class="form-control" required email-validator>
<div class="help-block" ng-messages="form.email.$error" ng-if="form.email.$dirty">
<p ng-message="required">Your name is required.</p>
<div ng-message="email" class="help-block" >email already in use</div>
</div>
Yes you can directly show error message into the HTML.
try this,
In HTML,
<form name="form" ng-submit="!form.$pending && form.$valid && vm.register()" role="form">
<div>
<label for="email">Email</label>
<input type="text" name="email" id="email" class="form-control" ng-model="vm.user.email" email-not-used ng-model-options="{ debounce: 500 }" required >
<span ng-show="form.email.$invalid && form.email.$dirty">Email is already in use!</span>
<span ng-show="form.email.$valid && form.email.$dirty">Email available!</span>
</div>
<div class="form-actions">
<button type="submit" ng-disabled="form.$invalid || vm.dataLoading" class="btn btn-primary">Register</button>
Cancel
</div>
</form>
In app.js,
var app = angular.module('app', []);
app.directive('emailNotUsed',emailNotUsed);
emailNotUsed.$inject = ['$http', '$q'];
function emailNotUsed ($http, $q) {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$asyncValidators.emailNotUsed = function(modelValue, viewValue) {
var data='';
if (viewValue=='abc#xyz.com'){
ngModel.$setValidity('valid', false);
scope.form.email.$setValidity('email', false);
}else{
ngModel.$setValidity('valid', true);
scope.form.email.$setValidity('email', true);
}
return data;
}
}
}
}
app.controller('RegisterController', RegisterController);
RegisterController.$inject = ['$scope', '$http','$q' ];
function RegisterController($scope, $http,$q) {
var vm = this;
$scope.name = 'abc#xyz.com email is already in use ';
vm.register = register;
function register() {
vm.dataLoading = true;
UserService.Create(vm.user)
}
}
Hope this helps. .
You can setup error message directly in your view. Bind to $scope.form.email.emailNotUsed.
<div>
<label for="email">Email</label>
<input type="text" name="email" id="email" class="form-control" ng-model="vm.user.email" email-not-used ng-model-options="{ debounce: 500 }" required >
<span ng-show="form.email.emailNotUsed">Email is already in use!</span>
</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>

Resources