Creating custom field validation in angular - angularjs

I have the following code/plunkr and for some reason the form.name.$invalid is always true (see the span just after the input box). Angular's documentation doesn't say much about how the $invalid value is set. I have a hunch it has something to do with in the javascript I have ctrl.$error.taken = true/false and just having an object on the $error object set's $invalid to true. Can someone who knows how angular works behind the scenes help me out?
I want the "Name must be alpha-numeric" error to display if the name doesn't match the regex, but I want the "Name is already taken" error to display if the name is one from the list. I also what the text "error" to display if the field is one of those two errors. All of these things are working except the word "error" is always displayed. I'm trying to follow angular's standards of using $invalid.
View:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-forms-async-validation-production</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="form-example1">
<form name="form" class="css-form" novalidate>
<div>
Username:
<input type="text" ng-model="name" name="name" username />
<span ng-show="form.name.$invalid">error</span>
<br />{{name}}<br />
<span ng-show="form.name.$error.badContent">Name must be alpha-numeric</span>
<span ng-show="form.name.$error.taken">Name is already taken!</span>
</div>
</form>
</body>
</html>
Script:
(function(angular) {
'use strict';
var app = angular.module('form-example1', []);
var NAME_REGEXP = /^([a-zA-Z0-9])*$/;
app.directive('username', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
var names = ['Jim', 'John', 'Jill', 'Jackie'];
ctrl.$validators.username = function(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
return true; // consider empty model valid
}
ctrl.$error.taken = names.indexOf(modelValue) > -1;
ctrl.$error.badContent = !NAME_REGEXP.test(modelValue);
return !ctrl.$error.taken && !ctrl.$error.badContent;
};
}
};
});
})(window.angular);
Plunkr:
https://plnkr.co/edit/LBRR14PpjAQigafOVTgQ?p=preview

Use separate directives one to validate alphanumeric, and one for the username already taken issue.
$ctrl.validators.alphaNumeric exposes $error.alphaNumeric on the FormController. You don't have to manipulate the value on $error.xxx, it is set automatically based on the validator's return value.
Please check the plunkr.
Also refer to https://code.angularjs.org/1.5.8/docs/api/ng/type/ngModel.NgModelController
Though I have not verified this, but I believe you now should have ng-invalid-alpha-numeric error class, owing to the fact that now form.$error.alphaNumeric is set.
JS
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.model = {};
});
app.directive('validateAlphaNumeric', function () {
var REGEX = /^([a-zA-Z0-9])*$/;
return {
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
ctrl.$validators.alphaNumeric = function (modelValue, viewValue) {
if (REGEX.test(viewValue)) {
return true
}
return false;
};
}
};
});
app.directive('validateUserNotTaken', function () {
return {
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
var names = ['Jim', 'John', 'Jill', 'Jackie'];
ctrl.$validators.userNotTaken = function (modelValue, viewValue) {
if (names.indexOf(modelValue) == -1) {
return true
}
return false;
};
}
};
});
HTML
<body ng-controller="MainCtrl">
<form name="myForm">
<input type="text" validate-alpha-numeric validate-user-not-taken ng-model="model.value1">
<p ng-if="myForm.$error.alphaNumeric">Should be alphanumeric</p>
<p ng-if="myForm.$error.userNotTaken">User Exists!</p>
<p ng-if="myForm.$error.alphaNumeric || myForm.$error.userNotTaken">Error</p>
</form>
</body>
Working plunkr: https://plnkr.co/edit/MZ7DMW?p=preview

Related

angular js, how to restrict input values to only a few numbers

I'm building an app in angular js.
I want to create a restriction an input field to accept only 3, 6, 9, 12, 15 and so on.
It should mark it as no valid if the user writes for example an 8.
I couldn't find how to do this, help would be appreciated.
You can create a directive and add your own validator that checks for multiples of 3.
See plunker
Markup:
<input name="numberField" type="number" data-ng-model="model.number" data-multiple-validator="3"/>
JS:
app.directive('multipleValidator', [function(){
return {
restrict: 'AC',
require: 'ngModel',
link: function(scope, elem, attr, ngModelCtrl) {
if (!ngModelCtrl) {
return;
}
var multiple = parseInt(attr.multipleValidator);
if (!multiple) {
return;
}
ngModelCtrl.$validators.multipleValidator = function(modelValue, viewValue) {
return !!modelValue && modelValue%multiple === 0;
}
}
}
}])
If it's only the numbers 3, 6, 9, 12 and 15 then you can just use ng-pattern like so
<input name="numberField" type="number" data-ng-model="model.number" data-ng-pattern="/^(3|6|9|12|15)$/"/>
EDIT
If your multiple value is dynamic then you will have to watch for changes in your directive using the very handy $observe and re-validate when it changes. See updated plunker.
Markup:
<input placeholder="input" name="numberField" type="number" data-ng-model="model.number" data-multiple-validator="{{model.multiple}}"/>
JS:
app.directive('multipleValidator', [function(){
return {
restrict: 'AC',
require: 'ngModel',
link: function(scope, elem, attr, ngModelCtrl) {
if (!ngModelCtrl) {
return;
}
ngModelCtrl.$validators.multipleValidator = function(modelValue, viewValue) {
var multiple = parseInt(attr.multipleValidator);
if (!multiple) {
// If there is no multiple then assume that it is valid
return true;
}
return !!modelValue && modelValue%multiple === 0;
}
// Watch for changes to the multipleValidator attribute
attr.$observe('multipleValidator', function(newVal, oldVal) {
// Re-validate the input when a change is detected
ngModelCtrl.$validate();
});
}
}
}]);
There is no need to use a custom directive. Angular has built in directive called ng-pattern which lets you match the input field to your given regular expression.
Here is how i have achieved this..
CODE...
<html ng-app="num">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
</head>
<body ng-controller="numCtrl">
<form class="digits" name="digits">
<input type="text" placeholder="digits here plz" name="nums" ng-model="nums" required ng-pattern="/^([0369]|[258][0369]*[147]|[147]([0369]|[147][0369]*[258])*[258]|[258][0369]*[258]([0369]|[147][0369]*[258])*[258]|[147]([0369]|[147][0369]*[258])*[147][0369]*[147]|[258][0369]*[258]([0369]|[147][0369]*[258])*[147][0369]*[147])*$/" />
<p class="alert" ng-show="digits.nums.$error.pattern">Multiples of three only accepted.</p>
<br>
</form>
<script>
var app = angular.module('num',[]);
app.controller('numCtrl', function($scope, $http){
$scope.digits = {};
});
</script>
</body>
</html>
See this jsfiddle for a working example https://jsfiddle.net/s7mmvxq5/5/
The approach I took was to create a directive that just adds an additional $validator to the form field. Since the validator is just a function you can do whatever check you'd like in it.
This section of the angular docs might be of interest https://docs.angularjs.org/guide/forms#custom-validation

AngularJS custom directive, cant access ngModel on DOM load

I'm using Laravel5 and creating a custom directive for angular novalidation forms. Now I can't access ngmodel placed data. My goal is to first show person's name, and then save his name, but name is not showing at DOM load...
Main problem I found is on $watch, but I don't know what to change in it.
Here's my codepen
AngularJS code:
(function() {
'use strict';
angular.module('app', [])
.controller('ctrl', mainController)
.directive('myInput', myInput);
function mainController() {
var vm = this;
vm.data = {
email: 'test#tesst.com',
name: 'Nickelson'
};
}
function myInput() {
function tempFunc(element, attrs) {
var templateWithVars = '<input type="inputType" ng-model="fieldModelName" name="fieldModelName" ng-required="required"/>';
var template = templateWithVars
.replace("inputType", attrs.type)
.replace("exampleName", attrs.example)
.replace(/fieldModelName/g, getFieldModelName(attrs));
return template;
}
function getFieldModelName(attrs) {
var objectAndField = attrs.ngModel;
var names = objectAndField.split('.');
var fieldModelName = names.pop();
return fieldModelName;
}
return {
replace: false,
scope: {},
template: tempFunc,
require: ['^form', "ngModel"],
link: function (scope, element, attrs, ctrls) {
scope.form = ctrls[0];
var ngModel = ctrls[1]; // This part is null
var model = getFieldModelName(attrs);
scope.$watch(model, function (newVal, oldVal) {
ngModel.$setViewValue(scope[model]);
});
}
}
}
})();
HTML:
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
</head>
<body ng-controller="ctrl as vm">
<br />
email: {{ vm.data.email }}
<br />
name: {{ vm.data.name }} // Nothing showing in here!!!
<br />
<h2>Edit</h2>
<form novalidation name="vm.form">
<my-input ng-model="vm.data.name"
type="text"
example="vm.data.name"> // My Input by default is empty
</my-input>
<br />
<br />
<input type="submit" ng-show="vm.form.$valid" value="Save Name"/>
</form>
</body>
</html>
You're assigning the ng-model to your directive in a funky way. It's making it such that it does work, but the way it initializes actually overwrites the original ng-model with the blank input.
If you change your $watch function to :
if (scope[model] !== undefined) ngModel.$setViewValue(scope[model]);
It'll only update the ngModel if it's not undefined, which it is initially.
Here's an updated codepen.
Note that you'll actually have to make changes to the input before ngModel is defined (even if you just type a space and backspace, it'll be fine).
I fix my problem. I don't exacly needed place ngmodel from require: ["ngModel"], but just add it in scope (scope: { ngModel: '=' }), and after that I add it strate to tempFunc, like:
var templateWithVars = 'input type="inputType" ng-model="ngModel" name="fieldModelName" ng-required="required"/>';
And It kind of fix it...

AngularJS custom validation $setValidity

Consider this runnable example: http://plnkr.co/edit/1BfO7KkHeMD3EpsULNGP?p=preview
<html ng-app='app'>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.4/angular.js"></script>
<script>
angular.module('app', [])
.directive('pwCheck', [function () {
return {
require: 'ngModel',
link: function (scope, elem, attrs, ctrl) {
var firstPassword = '#' + attrs.pwCheck;
elem.add(firstPassword).on('keyup', function () {
var v = elem.val()===$(firstPassword).val();
console.log(v);
ctrl.$setValidity('pwcheck', v);
});
}
}
}]);
</script>
<form name="form">
<input type="text" id="pw1" name="pw1" ng-model="pw1Model">
<input type="text" name="pw2" pw-check="pw1" ng-model="pw2Model">
Valid: {{form.pw2.$valid}}
<pre>{{form.pw2 |json}}</pre>
</form>
Write a character i one of the fields, and see that the validity is not updated until the second character is written in. The validity is not correct if you test to copy/paste etc. But the correct value v is logged. Why is the model not changed correctly?
Wrap $setValidity into $timeout
$timeout(function() {
ctrl.$setValidity('pwcheck', v);
});
http://plnkr.co/edit/OSuHBgtReDoCGWi03Cc8?p=preview
I assume that there is some conflict with the $setValidity call of angular native input directive.
Wrapping this call into $timeout makes it possible to avoid this conflict.

Integrate jquery masked input as angularjs directive

I wish to use masking in form inputs. I have created a directive uiMask which takes predefined masking formats like DoB or zip. In order to initiate masking, I apply the masking in directive's link function. And to update the model I manually trigger the digest cycle using $apply on keyup. Is this approach correct?
angular.module('formApp', [])
.controller("DemoFormController",['$scope',function($scope){
}])
.directive('uiMask', [
function () {
return {
require:'ngModel',
scope: {
type : "#uiMask"
},
controller: function($scope){
$scope.dob = "99/99/9999";
$scope.zip = "99999";
},
link:function ($scope, element, attrs, controller) {
var $element = $(element[0]);
$element.mask($scope.$eval($scope.type));
/* Add a parser that extracts the masked value into the model but only if the mask is valid
*/
controller.$parsers.push(function (value) {
var isValid = value.length && value.indexOf("_") == -1;
return isValid ? value : undefined;
});
/* When keyup, update the view value
*/
element.bind('keyup', function () {
$scope.$apply(function () {
controller.$setViewValue(element.val());
});
});
}
};
}
]);
<!doctype html>
<html ng-app="formApp">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
<script src="http://code.jquery.com/jquery-1.10.2.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery.maskedinput/1.3.1/jquery.maskedinput.js"></script>
</head>
<body>
<div ng-controller="DemoFormController" class="container">
{{employee | json}}
<input type="text" class="form-control" id="dob" name="dob" placeholder="Date of Birth" ui-mask="dob" ng-model="employee.dob">
<input type="text" class="form-control" id="zip" placeholder="Zip" ui-mask="zip" ng-model="employee.zip" >
</div>
</body>
</html>
I did something similar with putting a jQuery "plugin" (uniform.js) into an angular directive. I think what might help is if you attached a $watch to the element. Here is what I did for the uniform directive:
(function () {
'use strict';
angular.module('pfmApp').directive('uniform', function($timeout) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
$(element).addClass('uniform').uniform();
//$(element).uniform.update();
scope.$watch(function() { return ngModel.$modelValue }, function() {
$timeout(jQuery.uniform.update, 0);
});
}
}
});
}());
I hope this helps or perhaps points you in the right direction.

AngularJS and contentEditable two way binding doesn't work as expected

Why in the following example the initial rendered value is {{ person.name }} rather than David? How would you fix this?
Live example here
HTML:
<body ng-controller="MyCtrl">
<div contenteditable="true" ng-model="person.name">{{ person.name }}</div>
<pre ng-bind="person.name"></pre>
</body>
JS:
app.controller('MyCtrl', function($scope) {
$scope.person = {name: 'David'};
});
app.directive('contenteditable', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
// view -> model
element.bind('blur', function() {
scope.$apply(function() {
ctrl.$setViewValue(element.html());
});
});
// model -> view
ctrl.$render = function() {
element.html(ctrl.$viewValue);
};
// load init value from DOM
ctrl.$setViewValue(element.html());
}
};
});
The problem is that you are updating the view value when the interpolation is not finished yet.
So removing
// load init value from DOM
ctrl.$setViewValue(element.html());
or replacing it with
ctrl.$render();
will resolve the issue.
Short answer
You're initializing the model from the DOM using this line:
ctrl.$setViewValue(element.html());
You obviously don't need to initialize it from the DOM, since you're setting the value in the controller. Just remove this initialization line.
Long answer (and probably to the different question)
This is actually a known issue: https://github.com/angular/angular.js/issues/528
See an official docs example here
Html:
<!doctype html>
<html ng-app="customControl">
<head>
<script src="http://code.angularjs.org/1.2.0-rc.2/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<form name="myForm">
<div contenteditable
name="myWidget" ng-model="userContent"
strip-br="true"
required>Change me!</div>
<span ng-show="myForm.myWidget.$error.required">Required!</span>
<hr>
<textarea ng-model="userContent"></textarea>
</form>
</body>
</html>
JavaScript:
angular.module('customControl', []).
directive('contenteditable', function() {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function(scope, element, attrs, ngModel) {
if(!ngModel) return; // do nothing if no ng-model
// Specify how UI should be updated
ngModel.$render = function() {
element.html(ngModel.$viewValue || '');
};
// Listen for change events to enable binding
element.on('blur keyup change', function() {
scope.$apply(read);
});
read(); // initialize
// Write data to the model
function read() {
var html = element.html();
// When we clear the content editable the browser leaves a <br> behind
// If strip-br attribute is provided then we strip this out
if( attrs.stripBr && html == '<br>' ) {
html = '';
}
ngModel.$setViewValue(html);
}
}
};
});
Plunkr
Here is my understanding of Custom directives.
The code below is basic overview of two way binding.
you can see it working here as well.
http://plnkr.co/edit/8dhZw5W1JyPFUiY7sXjo
<!doctype html>
<html ng-app="customCtrl">
<head>
<script src="http://code.angularjs.org/1.2.0-rc.2/angular.min.js"></script>
<script>
angular.module("customCtrl", []) //[] for setter
.directive("contenteditable", function () {
return {
restrict: "A", //A for Attribute, E for Element, C for Class & M for comment
require: "ngModel", //requiring ngModel to bind 2 ways.
link: linkFunc
}
//----------------------------------------------------------------------//
function linkFunc(scope, element, attributes,ngModelController) {
// From Html to View Model
// Attaching an event handler to trigger the View Model Update.
// Using scope.$apply to update View Model with a function as an
// argument that takes Value from the Html Page and update it on View Model
element.on("keyup blur change", function () {
scope.$apply(updateViewModel)
})
function updateViewModel() {
var htmlValue = element.text()
ngModelController.$setViewValue(htmlValue)
}
// from View Model to Html
// render method of Model Controller takes a function defining how
// to update the Html. Function gets the current value in the View Model
// with $viewValue property of Model Controller and I used element text method
// to update the Html just as we do in normal jQuery.
ngModelController.$render = updateHtml
function updateHtml() {
var viewModelValue = ngModelController.$viewValue
// if viewModelValue is change internally, and if it is
// undefined, it won't update the html. That's why "" is used.
viewModelValue = viewModelValue ? viewModelValue : ""
element.text(viewModelValue)
}
// General Notes:- ngModelController is a connection between backend View Model and the
// front end Html. So we can use $viewValue and $setViewValue property to view backend
// value and set backend value. For taking and setting Frontend Html Value, Element would suffice.
}
})
</script>
</head>
<body>
<form name="myForm">
<label>Enter some text!!</label>
<div contenteditable
name="myWidget" ng-model="userContent"
style="border: 1px solid lightgrey"></div>
<hr>
<textarea placeholder="Enter some text!!" ng-model="userContent"></textarea>
</form>
</body>
</html>
Hope, it helps someone out there.!!
Check this angularjs directive
https://github.com/clofus/angular-inputnlabel
http://clofus.com/viewarticles/109
You may run into issues using #Vanaun's code if a scope.$apply is already in progress. In this case I use $timeout instead which resolves the issue:
Html:
<!doctype html>
<html ng-app="customControl">
<head>
<script src="http://code.angularjs.org/1.2.0-rc.2/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<form name="myForm">
<div contenteditable
name="myWidget" ng-model="userContent"
strip-br="true"
required>Change me!</div>
<span ng-show="myForm.myWidget.$error.required">Required!</span>
<hr>
<textarea ng-model="userContent"></textarea>
</form>
</body>
</html>
JavaScript:
angular.module('customControl', []).
directive('contenteditable', function($timeout) {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function(scope, element, attrs, ngModel) {
if(!ngModel) return; // do nothing if no ng-model
// Specify how UI should be updated
ngModel.$render = function() {
element.html(ngModel.$viewValue || '');
};
// Listen for change events to enable binding
element.on('blur keyup change', function() {
$timeout(read);
});
read(); // initialize
// Write data to the model
function read() {
var html = element.html();
// When we clear the content editable the browser leaves a <br> behind
// If strip-br attribute is provided then we strip this out
if( attrs.stripBr && html == '<br>' ) {
html = '';
}
ngModel.$setViewValue(html);
}
}
};
});
Working Example: Plunkr

Resources