Here is my custom directive:
angular
.module('accountApp')
.directive('uniqueRecord', function($q, $timeout, $http) {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$asyncValidators.uniqueRecord = function(modelValue, viewValue) {
var value = modelValue || viewValue;
var attributes = scope.$eval(attrs.uniqueRecord);
// Lookup effect by name
return $http.get(attributes.url + '/' + value + '/' + ((attributes.currentRecordName == '' || attributes.currentRecordName == 'nothing') ? '_' : attributes.currentRecordName))
.then(function resolved() {
//username exists, this means validation fails
return $q.reject('exists');
}, function rejected() {
//username does not exist, therefore this validation passes
return true;
});
};
}
}
});
Here is the HTML:
<input type="text" id="name" name="name" class="form-control form-input" ng-model="effect.name"
ng-disabled="isReadOnly" required
unique-record="{ url: '/api/effect', currentRecordName: {{currentEffectName == '' ? 'nothing' : currentEffectName}} }"
ng-uniqueRecord-err-type="duplicateRecord"/>
As you can see in the above HTML, I am passing the value of currentRecordName to directive. In directive the value of url is passed as is but the value of currentRecordName is always undefined. Why?
The problem comes from your utilisation of mustache in the evaled expression.
An evaled expression is already considered as an angular expression and does not need the mustache.
I suggest you to change your html to :
<input type="text" id="name" name="name" class="form-control form-input" ng-model="effect.name"
ng-disabled="isReadOnly" required
unique-record="{ url: '/api/effect', currentRecordName: currentEffectName }"
ng-uniqueRecord-err-type="duplicateRecord"/>
And handle the currentEffectName vs 'nothing' directly in the directive code.
var attributes = scope.$eval(attrs.uniqueRecord);
if (!attributes.currentRecordName) {
attributes.currentRecordName = 'nothing';
}
Related
I'm trying to implement an asynchronous validation by using a custom directive.
This is the directive
moduloArea.directive('uniqueName', function($http, $q) {
return {
require : 'ngModel',
link: function($scope, element, attrs, ngModel) {
ngModel.$asyncValidators.nombre = function(modelValue, viewValue) {
return $http.get('/checkUsernameAvailability/'+viewValue).then(
function(response) {
if (!response.data.validUsername) {
return $q.reject(response.data.errorMessage);
}
return true;
}
);
};
}
};
});
The result in console
As you can see when the root username is typed the return is an JSON object because this username is already taken.
But in the HTML the form in $invalid when the directive unique-name is inserted.
<form name="registerUsernameForm" novalidate="novalidate">
<input type="text" name="username" data-ng-model="person.userName" data-unique-name="" required="required"/>
<span data-ng-show="registerUsernameForm.username.$error.uniqueName">This username is already taken.</span>
<button type="submit" data-ng-disabled="registerUsernameForm.$invalid || registerUsernameForm.$pending" data-ng-click="registerPerson(person)"> Save Person </button>
</form>
I'm using the data-unique-name="" (="") because if I don't then thymeleaf generate the following error:
Attribute name "data-unique-name" associated with an element type
"input" must be followed by the ' = ' character.
What do you think can be wrong?
You are validating nombre not uniqueName. For this reason, uniqueName is still stay invalid.
moduloArea.directive('uniqueName', function($http, $q) {
return {
require : 'ngModel',
link: function($scope, element, attrs, ngModel) {
ngModel.$asyncValidators.uniqueName= function(modelValue, viewValue) {
var value = modelValue || viewValue;
return $http.get('/checkUsernameAvailability/'+value ).then(
function resolved(response) {
if (response.data && !response.data.validUsername) {
return $q.reject(response.data.errorMessage);
}
return true;
}, function rejected() {
//username does not exist, therefore this validation passes
return true;
}
);
};
}
};
});
something similar may have been answered (ng-pattern + ng-change) but all responses were unable to fix this issue.
I have two imbricated directives for creating a form input, a parent directive to control name, label, validator etc. and a child directive to set pattern and input type specific stuff.
However, when setting a pattern, the value on my model is set to undefined when ng-pattern return false.
Directives:
<input-wrapper ng-model="vm.customer.phone" name="phone" label="Phone number">
<input-text type="tel"></input-text>
</input-wrapper>
Generated HTML:
<label for="phone">Phone number:</label>
<input type="text" name="phone"
ng-model="value"
ng-model-options="{ updateOn: \'blur\' }"
ng-change="onChange()"
ng-pattern="/^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/">
JS:
angular.module('components', [])
.directive('inputWrapper', function() {
return {
restrict: 'E',
require: 'ngModel',
scope: true,
link: function (scope, element, attrs, ngModel) {
scope.name = attrs.name;
scope.label = attrs.label;
scope.onChange = function () {
ngModel.$setViewValue(scope.value);
};
ngModel.$render = function () {
scope.value = ngModel.$modelValue;
};
}
}
})
.directive('inputText', function() {
return {
restrict: 'E',
template: '<label for="{{name}}">{{label}}:</label><input type="text" name="{{name}}" ng-model="value" ng-model-options="{ updateOn: \'blur\' }" ng-change="onChange()" ng-pattern="pattern">',
link: function (scope, element, attrs) {
if (attrs.type === 'tel') {
scope.pattern = /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/;
}
}
}
});
angular.module('app',['components'])
.controller('ctrl',function($scope){
var vm = this;
vm.customer = {
phone: '010203040506'
};
});
What am I doing wrong ?
Codepen for use case: https://codepen.io/Yosky/pen/yVrmvw
By default in angular if a validator fail, undefined value assigned to ng-model, You can change this setting as follow :
<div ng-model-options="{ allowInvalid: true}">
read here for detail docs
I had some requirements that meant that I really really didn't want ng-model to write out undefined to the scope when validation was invalid, and I didn't want the invalid value either, so allowInvalid didn't help. In stead I just wanted ng-model do not write anything, but I couldn't find any option for this.
So I couldn't see any way forward except for doing some monkey patching of the ng-model controller.
So first I required ngModel in the component I was building require: { model: 'ngModel' } and then I did this in the $onInit hook:
const writeModelToScope = this.model.$$writeModelToScope;
const _this = this;
this.model.$$writeModelToScope = function() {
const allowInvalid = _this.model.$options.getOption('allowInvalid');
if (!allowInvalid && _this.model.$invalid) {
return;
}
writeModelToScope.bind(this)();
};
I also didn't want to take in a new model value while the value was invalid and the component had focus, so I did:
const setModelValue = this.model.$$setModelValue;
this.model.$$setModelValue = function(modelValue) {
_this.lastModelValue = modelValue;
if (_this.model.$invalid) {
return;
}
if (_this.hasFocus) {
return;
}
setModelValue.bind(this)(modelValue);
};
element.on('focus', () => {
this.hasFocus = true;
});
element.on('blur', (event) => {
this.hasFocus = false;
const allowInvalid = this.model.$options.getOption('allowInvalid');
if (!allowInvalid && this.model.$invalid) {
this.value = this.lastModelValue;
}
event.preventDefault();
});
Feel free to judge me, just know that I already feel dirty.
I was working in angular project, There I had come across a situation in which I need to validate custom component having a textbox.
<dollar-text-validate ng-model="ctrl.value" required name="myDir"></dollar-text-validate>
My Component
angular.module("myApp", []);
angular.module("myApp").controller('MyController',function(){
var ctrl = this;
ctrl.value = 56;
});
angular.module("myApp").component('dollarTextValidate',{
bindings: {
ngModel :'='
},
template: '<div><input type="text" ng-focus="ctrl.focus()" ng-blur="ctrl.blur()" ng-model="ctrl.amount1"><input type="hidden" ng-model="ctrl.ngModel"></div>',
controller: function() {
var ctrl = this;
// ctrl.amount1 =
ctrl.amount1 =ctrl.ngModel===undefined||ctrl.ngModel==='' ? '' :'$'+ctrl.ngModel;
console.log(ctrl.ngModel);
ctrl.focus=function(){
ctrl.amount1 = ctrl.amount1 === undefined ? '' : ctrl.amount1.slice(1);
ctrl.ngModel = ctrl.amount1;
console.log(ctrl.ngModel);
}
ctrl.blur=function(){
ctrl.ngModel = ctrl.amount1;
ctrl.amount1 = ctrl.amount1==='' ? '' :'$'+ctrl.ngModel;
console.log(ctrl.ngModel);
}
},
controllerAs:'ctrl'
})
This component is used to set $ symbol in front of entered value. So $ appended value will be available in textbox and original value which is to be validated in hidden field.
How can I validate hidden field. I tried with required attribute in hidden tag but nothing happening. Also tried with custom tag.
Sorry to break it to you, but you might wanna go for directive, and then use $parsers, $formatter and $validators properties of ngModelController.
Component can be used for this, but it is just easier with normal directive.
angular.module('myApp', []);
angular.module("myApp").directive('dollarTextValidate', function() {
return {
require: 'ngModel',
link: function($scope, $element) {
var regexp = /^\$(\d+(\.\d+)?)$/;
var ngModel = $element.controller('ngModel');
ngModel.$formatters.push(function(value) {
return value ? '$' + value : '';
});
ngModel.$parsers.push(function(value) {
var matched = value.match(regexp);
if (matched) {
return +matched[1];
}
});
ngModel.$validators.greaterThan10 = function (modelVal, viewVal) {
var value = modelVal || viewVal;
return value > 10;
};
},
controllerAs: 'ctrl'
};
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.js"></script>
<div ng-app="myApp" ng-form="form">
dollar-text-validate = <input dollar-text-validate ng-model="value" required name="myDir"><br>
number input = <input type="number" ng-model="value" required><br>
value = {{value}}<br>
errors = {{form.myDir.$error}}
</div>
I want to assign ng-model variable based on condition. For example:
<input type="text" ng-model="item.model[multilang]" >
$scope.multilang can be "ENG","JP"(languages) or false. For example, if multilang = "ENG" and user type in input "Hello",the result will be
item.model = {ENG:"Hello"}
The problem is when $scope.multilang = false , I want the result would be
item.model = "Hello"
I can't find a way to achieve above result. I think one solution is changing ng-model based on $scope.multilang so when it's false,it will change ng-model of input to be = ng-model="item.model" but I don't know how to do this.
EDITED
I thought of one solution:
<input ng-if="multilang" type="text" ng-model="item.model[multilang]" >
<input ng-if="!multilang" type="text" ng-model="item.model" >
but there is better way to achieve ?
-----plnkr example-----
Angular is very flexible and powerful framework. You should use custom directive and ngModel's getter/setter option.
Directive without ngModel's getter/setter may look like this:
<input type="text"
ng-model="val"
multilang="multilang"
multilang-model="item.model">
Directive code:
.directive('multilang', [function(){
return {
restrict: "A",
require: "ngModel",
scope: {
multilang: "=",
multilangModel: "="
},
link: function(scope, element, attr, ngModel){
ngModel.$viewChangeListeners.push(function()){
var value = ngModel.$modelValue;
if(scope.multilang !== false) {
if(typeof scope.multilangModel == 'undefined')
scope.multilangModel = Object.create(null)
scope.multilangModel[scope.multilang] = value
}
else {
scope.multilangModel = value
}
})
}
}
}])
--forked plunkr--
In case of using ngModel's getter/setter
<input type="text"
ng-model="val"
multilang="multilang"
multilang-model="item.model"
ng-model-options="{ getterSetter: true }">
Directive code:
.directive('multilang', [function(){
return {
restrict: "A",
scope: {
multilang: "=",
multilangModel: "=",
val: "=ngModel"
},
link: function(scope, element, attr){
scope.val = function(newValue) {
if(scope.multilang !== false) {
if(typeof scope.multilangModel == 'undefined')
scope.multilangModel = Object.create(null)
return arguments.length ? (scope.multilangModel[scope.multilang] = newValue) : scope.multilangModel[scope.multilang];
}
else {
return arguments.length ? (scope.multilangModel = newValue) : scope.multilangModel;
}
}
}
}
}])
--forked plunkr--
In my opinion, second one is better. It has two way binding with item.model and changes input value when item.model was changed in other place of code.
Try this:
<input ng-show="multilang" type="text" ng-model="item.model[multilang]" >
<input ng-hide="multilang" type="text" ng-model="item.model" >
I have written a custom directive to ensure that a text box can take only integer values.
But After using the directive, my $error.required flag always stay false irrespective of whether I have a value in the text field or not.
It works fine if I use the native input type,
<input name="name" ng-model="testvalue.number" required />
but when I use the custom directive,
<number-only-input input-value="testvalue.number" input-name="name"/>
shippingForm.name.$error.required is always false and it doesn't show the error "Please enter a value" even if the field is empty
This is the code
<body ng-controller="ExampleController">
<form name="shippingForm" novalidate>
<!--<input name="name" ng-model="testvalue.number" required />-->
<number-only-input input-value="testvalue.number" input-name="name"/>
<span class="error" ng-show="shippingForm.name.$error.required">
Please enter a value
</span>
</form>
</body>
<script>
angular.module('TextboxExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.testvalue = {number: 1, validity: true}
}])
.directive('numberOnlyInput', function () {
return {
restrict: 'EA',
template: '<input name="{{inputName}}" ng-model="inputValue" required />',
scope: {
inputValue: '=',
inputName: '='
},
link: function (scope) {
scope.$watch('inputValue', function(newValue,oldValue) {
if (newValue == undefined || newValue == null || newValue == "") {
return;
}
if (isNaN(newValue))
{
scope.inputValue = oldValue;
return;
}
});
}
};
});
</script>
Please guide
Sunil
You code has some flaws, but i think this is because you made the example incorrect, the main issue may be here:
inputName: '=' should be replaced with inputName: '#' to hold the string value of the input name
I'm not quite sure what's wrong with your example, it just doesn't work... Anyway here's a workaround which you can use to implement your functionality.
HTML:
<div ng-app="TextboxExample" ng-controller="ExampleController">
<form name="shippingForm" novalidate>
<input number-only-input type="text" name="name" ng-model="testvalue.number" required />
<!-- <number-only-input input-value="testvalue.number" input-name="name"/> -->
<span class="error" ng-show="shippingForm.name.$error.required">
Please enter a value
</span>
</form>
</div>
Controller:
angular.module('TextboxExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.testvalue = {number: 1, validity: true};
}])
.directive('numberOnlyInput', function () {
return {
link: function (scope, element, attrs) {
var watchMe = attrs["ngModel"];
scope.$watch(watchMe, function(newValue,oldValue) {
if(newValue == undefined || newValue == null || newValue == ""){
return;
} else if (isNaN(newValue)) {
var myVal = watchMe.split(".");
switch (myVal.length) {
case 1:
scope[myVal[0]] = oldValue;
break;
case 2:
scope[myVal[0]][myVal[1]] = oldValue;
}
}
});
}
};
});