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
Related
I have 2 directives: calculatorForm and calculatorAttribute. CalculatorForm is the parent directive, specifically a form which contains input tags which are calculatorAttribute directives.
I want the calculatorAttribute call calculatorForm function that changes a scope variable and trigger a watcher.
Here's my code:
angular
.module('calculator')
.directive('calculatorForm', ['CalculatorDataModel', 'CalculatorPriceModel',
function(CalculatorDataModel, CalculatorPriceModel) {
return {
restrict : 'A',
replace : true,
templateUrl : function(element, attrs) {
return attrs.templateUrl;
},
link : function(scope, element, attrs) {
scope.model = CalculatorDataModel;
scope.price = CalculatorPriceModel;
scope.model.initialize(calculator_data);
scope.updateSelectedSpecs = function(attribute_id, prod_attr_val_id) {
var selected_specs = JSON.parse(JSON.stringify(scope.model.selected_specs));
selected_specs[attribute_id] = prod_attr_val_id;
scope.model.selected_specs = selected_specs;
}
scope.$watch('model.selected_specs', function(selected_specs, previous_selected_specs) {
if (selected_specs != previous_selected_specs) {
scope.model.setCalculatorData();
scope.price.computePrice();
}
});
}
}
}
])
.directive('calculatorAttribute', [
function() {
return {
restrict : 'A',
template : "<input type='radio' name='attr{{attribute_id}}' ng-value='prod_attr_val_id'/>",
replace : true,
link : function(scope, element, attrs) {
scope.attribute_id = attrs.attributeId;
scope.prod_attr_val_id = attrs.prodAttrValId;
element.on('click', function() {
scope.$parent.updateSelectedSpecs(scope.attribute_id, scope.prod_attr_val_id);
});
}
}
}
]);
My problem is updateSelectedSpecs in the parent is called but watcher has never been triggered when I use element.on click in the child directive.
Please help everyone Thank you!!!
Okay, after wrestling with this for a bit, I managed to produce a working version of a slimmed-down example:
angular.module('myApp', [])
.directive('calculatorForm', function() {
return {
restrict: 'A',
replace: true,
transclude: true,
template: '<div ng-transclude></div>',
link: function(scope, element, attrs) {
scope.model = {};
scope.price = {};
scope.updateSelectedSpecs = function(attribute_id, prod_attr_val_id) {
scope.$apply(function() {
console.log('update selected specs');
var selected_specs = {};
selected_specs[attribute_id] = prod_attr_val_id;
scope.model.selected_specs = selected_specs;
});
}
scope.$watch('model.selected_specs', function(selected_specs, previous_selected_specs) {
console.log('new selected specs', selected_specs, previous_selected_specs);
if (selected_specs != previous_selected_specs) {
console.log("and they're different");
}
});
}
};
})
.directive('calculatorAttribute', function() {
return {
restrict: 'A',
template: "<input type='radio' name='attr{{attribute_id}}' ng-value='prod_attr_val_id'/>",
replace: true,
link: function(scope, element, attrs) {
scope.attribute_id = attrs.attributeId;
scope.prod_attr_val_id = attrs.prodAttrValId;
element.on('click', function() {
scope.$parent.updateSelectedSpecs(scope.attribute_id, scope.prod_attr_val_id);
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<form calculator-form ng-app="myApp">
<input calculator-attribute attribute-id=1 prod-attr-val-id=1>
</form>
Just look at the console to see it getting into the $watch. The problem seemed to be the fact that you didn't trigger a $digest cycle in your updateSelectedSpecs function. Usually a $timeout, $http call, or ngClick or other event would start the $digest cycle for you, but in this case you have to start it yourself using scope.$apply().
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();
}
}
I've got following script
// Code goes here
angular.module('default', [])
.directive('webAbc', function($log) {
return {
restrict: 'A',
controller: function($scope, $element, $attrs, $transclude) {
this.checkboxes = [];
this.updateLinkedCheckboxes = function(value) {
angular.forEach(this.checkboxes, function(checkbox, index) {
checkbox.setChecked(value);
});
};
}
};
})
.directive('webDef', function($log) {
return {
restrict: 'C',
require: '^webAbc',
link: function (scope, iElement, iAttrs, webAbc, transcludeFn) {
iElement.bind('change', function () {
webAbc.updateLinkedCheckboxes(iElement.prop('checked'));
scope.$apply();
});
}
};
})
.directive('webGhi', function($log) {
return {
restict: 'A',
require: '^webAbc',
link: function (scope, iElement, iAttrs, webAbc, transcludeFn) {
scope.setChecked = function(value) {
$log.log('This element ' + iAttrs.name + ' cheked: ' + (!value ? 'checked' : ''));
$log.log(value);
if (value)
{
iElement.attr('checked', 'checked');
}
else
{
iElement.remoteAttr('checked');
}
};
webAbc.checkboxes.push(scope);
}
};
});
it should select or deselect all checkboxes in table in marked column, but I can't make it work with following solution.
First of all it seems, that only last webGhi is visible due to print out in console. And even more, it seems, that I can't uncheck checkbox for some reason.
Link to an example: http://jsbin.com/difihabe/1/
Thank you.
Use an isolated scope in the webGhi directive or all four instances of it will push the same scope (the parent):
.directive('webGhi', function($log) {
return {
restict: 'A',
require: '^webAbc',
scope: {},
link: ...
Also instead of adding/removing the checked attribute either use:
jQuery's prop() function: iElement.prop('checked', value);
Directly setting the DOM element's checked property:
iElement[0].checked = value;
Demo: http://jsbin.com/tudotugi/2/
http://jsfiddle.net/mato75/t48qn/
I have a directive, that if id is not passed, then it should generate one, but it looks like that the generation is to slow and the id is not present in the directive.
(function (angular) {
'use strict';
angular.module('Widgets.Module')
.directive('myDirective', [
function () {
function postLink(scope, jqElm, attr) { }
function postCompile(tElement, tAttrs) {
return function postLink(scope, jqElm, attr) {
attr.$observe("id", function (id) { // called on on init
scope.id = id !== undefined ? id : 'something 1';
});
}
}
function Ctrl(scope) {
}
return {
template:
'<div id="{{ id }}">' +
'</div>',
controller: [
'$scope', Ctrl
],
replace: true,
scope: {
id: '#'
},
restrict: 'AC',
link: postLink,
compile: postCompile
};
}
])
;
})(window.angular)
I think using id is special since its a valid DOM attribute. In my case id was also getting added as an attribute to the directive html, not the inner child where I was using it.
I created a new attribute called input-id that doesn't suffer from this name collision.
<autosuggest input-id="country"></autosuggest>
The produced markup is:
<div class="autosuggest"><input id="country"></div>
...which is what I think you are after.
The scope block for the directive looks like this:
scope: {
inputId: '#'
}
One possible solution is to disable the automatic data binding (scope: {}) and do it manually in your link function.
Check this fiddle.
module.directive('myDialog', function () {
return {
replace: true,
restrict: 'E',
scope: {},
template: '<div>Test {{a1}}</div>',
link: function (scope, element, attrs) {
if (!attrs.a1) {
scope.a1 = "default";
} else {
scope.a1 = attrs.a1;
}
}
}
});
I have an AngularJS directive. My directive is restricted to be used as an attribute. However, I want to get the value of the attribute. Currently, I have my attribute defined as:
angular.module('myDirectives.color', [])
.directive('setColor', function () {
return {
retrict: 'A',
link: {
pre: function () {
console.log('color is: ');
}
}
};
})
;
I use the attribute in the following manner:
<button type="button" set-color="blue">Blue</button>
<button type="button" set-color="yellow">Yellow</button>
I know that my directive is being used because I see 'color is: ' However, I can't figure out how to make it display 'blue' or 'yellow' at the end of that console statement. I want to get that value so I can do a conditional processing. How do I get the value of the attribute?
Thank you!
You can use the attributes argument of the link function:
angular.module('myDirectives.color', [])
.directive('setColor', function () {
return {
restrict: 'A',
link: {
pre: function (scope, element, attrs) {
console.log('color is: ' + attrs.setColor);
}
}
};
});
or you could create an isolate scope like this:
angular.module('myDirectives.color', [])
.directive('setColor', function () {
return {
restrict: 'A',
scope: {
setColor: '#'
},
link: {
pre: function (scope) {
console.log('color is: ' + scope.setColor);
}
}
};
});