I have been trying to create a very simple directive that checks if the attribute my-minlength is a string (not null) and if it is; add the attribute ng-minlength to the input element. I feel like this should work, but it is simply not working.
My Directive
var app = angular.module("fooApplication", [])
app.directive('myMinlength', function() {
return {
link: function(scope, element, attrs) {
if(element == "") {
attrs.$set('ng-minlength', attrs.myMinlength);
}
},
}
});
My HTML
<input type="text" name="foo" my-minlength="5" required/>
Edit the suggestion completely stripped from possible errors - still does not work.
.directive('myMinlength', ['$compile', function($compile) {
return {
link: function(scope, element, attrs) {
if(true) {
attrs.$set('ng-minlength', "5");
$compile(element)(scope);
}
},
}
}]);
You can use simpler approach:
<input type="text" name="foo" ng-minlength="myvar || 0" required/>
if scope.myvar is undefined then minlength will be 0
You need to recompile your directive in order to add the ng-minlength attribute to your directive. Try this :
.directive('myMinlength', ['$compile', function($compile) {
return {
link: function(scope, element, attrs) {
if(element == "") {
attrs.$set('ng-minlength', attrs.myMinlength);
$compile(element)(scope);
}
},
}
}]);
you should use compile
.directive('myMinlength', function() {
var directive = {};
directive.restrict = 'A'; /* restrict this directive to attributess */
directive.compile = function(element, attributes) {
var linkFunction = function($scope, element, attributes) {
attributes.$set('ng-minlength', attrs.myMinlength);
}
return linkFunction;
}
return directive;
})
Related
I'm attempting to use multiple angular validators on a text field, but have run into the Multiple directives requesting isolated scope error. (Please read on before closing as a duplicate.)
All the solutions I've seen so far, recommend removing the scope: {...} from the offending directives, however for my scenario, I need to evaluate variables from the controller (and $watch them for changes).
I've tried using attrs.$observe, but I can't work out how to get the evaluated variables into the $validator function. (Additionally, I can't $observe the ngModel).
Please let me know if there's a another way to solve this issue.
Here's the smallest example I could put together. N.B. maxLength validator's scope is commented out, essentially disabling it:
angular
.module('app', [])
// validates the min length of a string...
.directive("minLen", function() {
return {
require: 'ngModel',
restrict: 'A',
scope: {
ngModel: '=',
minLen: '='
},
link: function(scope, element, attrs, ngModelCtrl) {
scope.$watch('ngModel', function(){
ngModelCtrl.$validate();
});
scope.$watch('minLen', function(){
ngModelCtrl.$validate();
});
ngModelCtrl.$validators.minLength = function(modelValue, viewValue) {
var value = modelValue || viewValue;
return angular.isUndefined(scope.minLen) ||
angular.isUndefined(value) ||
value.length >= scope.minLen;
};
}
};
})
.directive("maxLen", function() {
return {
require: 'ngModel',
restrict: 'A',
// Commented out for now - causes error.
// scope: {
// ngModel: '=',
// maxLen: "="
// },
link: function(scope, element, attrs, ngModelCtrl) {
scope.$watch('ngModel', function(){
ngModelCtrl.$validate();
});
scope.$watch('maxLen', function(){
ngModelCtrl.$validate();
});
ngModelCtrl.$validators.maxLength = function(modelValue, viewValue) {
var value = modelValue || viewValue;
return angular.isUndefined(scope.maxLen) ||
angular.isUndefined(value) ||
value.length >= scope.maxLen;
};
}
};
})
// this controller just initialises variables...
.controller('CustomController', function() {
var vm = this;
vm.toggleText = function(){
if (vm.text === 'aaa') {
vm.text = 'bbbbb';
} else {
vm.text = 'aaa';
}
}
vm.toggle = function(){
if (vm.minLen === 3) {
vm.minLen = 4;
vm.maxLen = 12;
} else {
vm.minLen = 3;
vm.maxLen = 10;
}
};
vm.toggleText();
vm.toggle();
return vm;
})
;
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div ng-app="app" ng-controller="CustomController as ctrl">
<ng-form name="ctrl.form">
<label>Enter {{ctrl.minLen}}-{{ctrl.maxLen}} characters: </label>
<input
name="text"
type="text"
ng-model="ctrl.text"
min-len="ctrl.minLen"
max-len="ctrl.maxLen"
/>
</ng-form>
<br/><br/>
<button ng-click="ctrl.toggle()">Modify validation lengths</button>
<button ng-click="ctrl.toggleText()">Modify ngModel (text)</button>
<h3>Validation (just list $error for now)</h3>
<pre>{{ ctrl.form.text.$error | json }}</pre>
</div>
remove isolated scope
You dont need to observe, watch ngModel - when it changes, angular will run validator for you.
Decide how you want to use your directive: my-val-dir="{{valName}}" vs my-val-dir="valName". In first case you use attrs.$observe('myValDir'), in second $watch(attrs.myValDir) & $eval.
When your value gonna be something simple, like number or short string - first way seems good, when value is something big, i.e. array - use second approach.
Remove isolate scope and use scope.$watch to evaluate the attribute:
app.directive("minLen", function() {
return {
require: 'ngModel',
restrict: 'A',
scope: false,
// ngModel: '=',
// minLen: '='
// },
link: function(scope, element, attrs, ngModelCtrl) {
var minLen = 0;
scope.$watch(attrs.minLen, function(value){
minLen = toInt(value) || 0;
ngModelCtrl.$validate();
});
ngModelCtrl.$validators.minLength = function(modelValue, viewValue) {
return ngModelCtrl.$isEmpty(viewValue) || viewValue.length >= minLen;
};
}
};
})
There is no need to watch the ngModel attribute as the ngModelController will automatically invoke the functions in the $validators collection when the model changes.
When the watch expression is a string, the string attribute will be evaluated as an Angular Expression.
For more information, see AngularJS scope API Reference - $watch.
I have this oversimplified directive for getting a phone number, and I want the ng-model to be invalid if the phone number is less than 10 digits. I was testing out $setValidity but it's not says that is not a function.
angular.module('valkyrie').directive('phonenumber', function(){
return{
scope: {phonemodel: '=model'},
template: '<input ng-model="inputValue" type="tel" class="form-control">',
link: function(scope, element, attributes){
scope.$watch('inputValue', function(value, oldValue) {
value = String(value);
var number = value.replace(/[^0-9]+/g, '');
scope.phonemodel = number;
scope.phonemodel.$setValidity('phone', false);
console.log(scope.phonemodel);
});
},
}
});
As Aluan Haddad mentioned in the comments, you need to require the ngModel directive within your directive. That will give you access to the ngModelController, and then you can just do ngModelController.$setValidity.
See a working demo here: http://codepen.io/miparnisari/pen/LxPoVw.
angular.module('valkyrie', [])
.controller('parent', function ($scope) {
$scope.phone = '';
})
.directive('phonenumber', function() {
return {
require: 'ngModel',
template: '<input ng-model="inputValue" type="tel" class="form-control">',
link: function(scope, element, attributes, ctrl) {
scope.$watch('inputValue', function(value, oldValue) {
if (!value) return;
scope.ngModel = value.replace(/[^0-9]+/g, '');
if (scope.ngModel.length < 10)
ctrl.$setValidity('phoneLength', false);
else
ctrl.$setValidity('phoneLength', true);
});
},
};
});
I wrote a directive for input focus & blur
angular
.module('app')
.directive('input', ['$filter', function($filter) {
return function(scope, element, attrs) {
if (element && element[0] && element[0].placeholder) {
scope.placeholder = element[0].placeholder;
element.bind("focus", function() {
console.log(scope.placeholder);
element[0].placeholder = "";
});
element.bind("blur", function() {
element[0].placeholder = $filter('translate')(scope.placeholder);
});
}
};
}]);
I want same functionality for text area also. But don't wanted to write an other directive. How I can do this?
var myDirective = ['$filter', function($filter) {
return {
restrict: 'E',
scope: true,
link: function(scope, element, attrs) {
if (element && element[0] && element[0].placeholder) {
scope.placeholder = element[0].placeholder;
element.bind("focus", function() {
console.log(scope.placeholder);
element[0].placeholder = "";
});
element.bind("blur", function() {
element[0].placeholder = $filter('translate')(scope.placeholder);
});
}
}
};
}]
angular
.module('app')
.directive('input', myDirective);
.directive('textarea', myDirective);
and in your html:
<input />
<textarea></textarea>
I have the following HTML / Angular code on a form:
<span class="error" ng-if="model.errors['message.email']" ng-bind="model.errors['message.email'][0]"></span>
I would like to use less code so the following would render the same:
<span class="error" model-validator="message.email" validator-errors="model.errors"></span>
Note
When validator-errors is not present then the default would be "errors" so I would get:
<span class="error" ng-if="errors['message.email']" ng-bind="errors['message.email'][0]"></span>
UPTATE 1
I tried the following:
.directive('modelValidator', function () {
return {
restrict: 'A',
replace: false,
link: function (scope, element, attrs) {
var validator = element.attr('model-validator');
if (validator === "null")
return;
var errors = element.attr('validator-errors');
element.attr('data-ng-if', errors + "['" + validator + "']");
element.attr('data-ng-bind', errors + "['" + validator + "'][0]");
}
};
But this is not adding the attributes ...
UPDATE 2
The directive is working. I would like to just add a few things:
How to use an attribute to specify which variable contains the errors?
On the directive "scope.model.errors" would be "scope.allErrorsToDisplay".
Do I need to watch all scope? Can I only watch model.errors?
Or considering (1), watch the variable in "validator-errors"?
Here is my current code:
angular.module('Application')
.directive('validator', function () {
return {
restrict: 'A',
replace: false,
link: function (scope, element, attribute) {
scope.$watch(function () {
if (scope.model.errors) {
if (scope.model.errors[attribute.validator]) {
element.show();
element.text(scope.model.errors[attribute.validator][0]);
} else
element.hide();
} else
element.hide();
});
}
}
});
Update 3
This seems to do everything I need.
Does anyone has any suggestion to improve it?
angular.module('app')
.directive('validator', ['$parse', function ($parse) {
return {
restrict: 'A',
replace: false,
link: function (scope, element, attributes) {
scope.$watch(function () {
var errors = $parse('validatorErrors' in attributes ? attributes["validatorErrors"] : 'model.errors')(scope);
if (errors) {
if (errors[attributes.validator]) {
element.show();
element.text(errors[attributes.validator][0]);
} else
element.hide();
} else
element.hide();
});
}
}
}]);
I think your approach is too complicated. The power of directives is that you don't have to add other directives to accomplish what you want. If i'm understanding your question correctly, you want your element to display a message if the error exists. How about this?
<!-- html -->
<div ng-controller="mainController">
<div validate="email"></div>
<div validate="name"></div>
</div>
// controller
function mainController ($scope) {
$scope.model = {
errors: {
email: 'Your email is invalid!'
, name: undefined
}
}
}
// directive
function validate () {
return {
restrict: 'A'
, replace: false
, link: function (scope, elem, attr) {
if (scope.model.errors[attr.validate]) {
elem.text(scope.model.errors[attr.validate]);
}
}
}
}
codepen
I do not want a user to enter spaces in a text field. I don't want it on submit validation but rather - a space will not show up on the text field when they click it.
The selected answer is arguably not very unobtrusive. And if you need to use it in multiple places, you'll end up with duplicated code.
I prefer to prevent the input of spaces using the following directive.
app.directive('disallowSpaces', function() {
return {
restrict: 'A',
link: function($scope, $element) {
$element.bind('input', function() {
$(this).val($(this).val().replace(/ /g, ''));
});
}
};
});
This directive can be invoked like this:
<input type="text" disallow-spaces>
<input ng-model="field" ng-trim="false" ng-change="field = field.split(' ').join('')" type="text">
Update:
To improve code quality you can create custom directive instead. But don't forget that your directive should prevent input not only from keyboard, but also from pasting.
<input type="text" ng-trim="false" ng-model="myValue" restrict-field="myValue">
Here is important to add ng-trim="false" attribute to disable trimming of an input.
angular
.module('app')
.directive('restrictField', function () {
return {
restrict: 'AE',
scope: {
restrictField: '='
},
link: function (scope) {
// this will match spaces, tabs, line feeds etc
// you can change this regex as you want
var regex = /\s/g;
scope.$watch('restrictField', function (newValue, oldValue) {
if (newValue != oldValue && regex.test(newValue)) {
scope.restrictField = newValue.replace(regex, '');
}
});
}
};
});
If you want to achieve it without writing directive
ng-keydown="$event.keyCode != 32 ? $event:$event.preventDefault()"
THe directive Jason wrote did not work for me. I had to change return false to: e.preventDefault() like so:
app.directive('disallowSpaces', function() {
return {
restrict: 'A',
link: function($scope, $element) {
$element.bind('keydown', function(e) {
if (e.which === 32) {
e.preventDefault();
}
});
}
}
});
This works to prevent entering any special chars including spaces:
app.directive('noSpecialChar', function() {
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, element, attrs, modelCtrl) {
modelCtrl.$parsers.push(function(inputValue) {
if (inputValue == null)
return ''
let cleanInputValue = inputValue.replace(/[^\w]|_/gi, '');
if (cleanInputValue != inputValue) {
modelCtrl.$setViewValue(cleanInputValue);
modelCtrl.$render();
}
return cleanInputValue;
});
}
}
});
Use without jquery
angular.module('app').directive('disallowSpaces', function () {
return {
restrict: 'A',
require: 'ngModel',
scope: {
maxvalue: '=',
},
link: function ($scope, $element, attr, ngModelCtrl) {
$element.bind('keydown', function () {
function transformer(text) {
if (text) {
var transformedInput = text.replace(/ /g, '');
ngModelCtrl.$setViewValue(transformedInput);
ngModelCtrl.$render();
return transformedInput;
}
return undefined;
}
ngModelCtrl.$parsers.push(transformer);
});
},
};
});
// use disallow-spaces
<input type="text" ng-model="name" disallow-spaces />
You can achieve this without writing a directive.
<input ng-model="myModel" ng-keydown="$event.keyCode != 32 ? $event:$event.preventDefault()">
For Angular 9 ,Keycode is not support.
Below code can help you for that.
keyDownHandler(event) {
if (event.code === 'Space') {
event.preventDefault();
}
}