Getting angular directive attribute value returns 'undefined' - angularjs

I'm doing a directive for input mask. But, when I pass a string as value the attribute is undefined. If I pass directly the mask It's working.
.directive('inputMask', function () {
return {
restrict: 'EAC',
scope: true,
link: function (scope, element, attrs) {
scope.$watch('inputMask', function (newVal) {
console.log('inputMask', newVal);
});
var maskType = scope.$eval(attrs.inputMask);
switch (maskType) {
case 'phone':
$(element).inputmask("phone", {
url: '#Url.Content("~/Scripts/jquery-inputmask/phone-codes/phone-codes.json")',
onKeyValidation: function () { //show some metadata in the console
console.log($(this).inputmask("getmetadata")["name_en"]);
}
});
break;
case 'money':
$(element).inputmask("decimal", { digits: 2 });
break;
case 'moneyGrouped':
$(element).inputmask("decimal", {
radixPoint: ",",
autoGroup: true,
groupSeparator: ".",
groupSize: 3,
digits: 2
});
break;
case 'email':
$(element).inputmask('Regex', { regex: "[a-zA-Z0-9._%-]+#[a-zA-Z0-9-]+\\.[a-zA-Z]{2,4}" });
default:
$(element).inputmask(maskType);
}
$(element).inputmask(scope.$eval(attrs.inputMask));
$(element).on('keypress', function () {
scope.$eval(attrs.ngModel + "='" + element.val() + "'");
});
}
};
});
Working (will get into default of the switch):
<input type="teste" name="teste" value="" ng-model="form.email" input-mask='{ "mask": "d/m/y", "autoUnmask" : true}'/>
Not working, attrs.inputMask is undefined (should enter in case 'money'):
<input type="teste" name="teste" value="" ng-model="form.email" input-mask='money'/>
What is wrong?

When you use scope: true a new scope will be created for this directive. Then, to your $watch works correctly, you should create a new attribute to the current scope, called inputMask, that receives the attrs.inputMask
scope.inputMask = attrs.inputMask;
scope.$watch('inputMask', function (newVal) {
console.log('inputMask', newVal);
});
You can see a simplified Working fiddle here
The other option, is to use the a hash object in directive's scope attribute.
The directive docs writes:
{} (object hash) - a new 'isolate' scope is created. The 'isolate'
scope differs from normal scope in that it does not prototypically
inherit from the parent scope. This is useful when creating reusable
components, which should not accidentally read or modify data in the
parent scope.
(...)
# or #attr - bind a local scope property to the value of a DOM attribute.
That way, you can create your scope binding the DOM attribute:
scope: {
inputMask: "#"
},
link: function (scope, element, attrs) {
scope.$watch('inputMask', function (newVal) {
console.log('inputMask', newVal);
});
/* ... */
}
Fiddle

In the directive use,
scope: {inputMask: '#'}
And in the link function instead of using attr.inputMask use scope.inputMask and that will work.
If you want to use attr then you can use
attr.$observe('inputMask', function() {
console.log('changed');
}
Because initially the value will be undefined.

The actual problem here was that scope.$eval('money') will return undefined.
The attribute should be linking just fine to the directive, if it's in curly braces {}, or if it's a string such as 'money'.
It's what you're doing with the value that's causing the problem.
var maskType = scope.$eval(attrs.inputMask);
You would only need to use isolated scope with #, or attrs.$observe if you were passing interpolated attributes, such as {{money}}.

Related

Call Parent Controller's Function from Directive

I'm trying to call a parent controller's function from a custom directive upon choosing an option from a select dropdown, but it keeps giving me ng:cpws error.
Error: [ng:cpws] http://errors.angularjs.org/1.4.8/ng/cpws
HTML:
<select chosen ng-model="selectedValue.recipientCountry" ng-options="x.Name.toLowerCase() for x in data.RecipientCountries" >
<option value=""></option>
</select>
JS:
uiComponentsModule.directive('chosen', [ function () {
return {
link: function(scope, element, attrs) {
scope.$watch(function (newVal, oldVal) {
scope.$parent.selectRecipientCountry(x)
})
}
}
}])
I'm trying to modify someone else's code, so it's hard to know exactly what's going on.
You need to pass the function to the directive from the parent controller, like below (I am free-handing without testing because you didn't provide a plunk, so I am sure you will need to adjust the code. The point is that the function gets passed as a param. If you don't know how to pass variables to directives from the parent controller, you won't understand this, so read up on that first). Note that I added 'scope' to your directive - that's where you define params for your directive, to be passed to the new scope of your directive:
Your directive in the html:
<chosen select-recepient-country = "selectRecipientCountry"></chosen>
Your directive code:
uiComponentsModule.directive('chosen', [ function () {
return {
scope: {
selectRecipientCountry: '&selectRecipientCountry',
},
link: function(scope, element, attrs) {
scope.$watch(function (newVal, oldVal) {
scope.selectRecipientCountry(x)
})
}
}
}])
For en explanation, see the article here:
http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters
You are looking for:
Option 2: Storing a Function Reference and Invoking It
Edit: We have a good example of passing functions here:
AngularJS - pass function to directive
Also note that:
scope: {
myFunction: '&',
}
and
scope: {
myFunction: '&myFunction',
}
are equivalent. One just explicitly names the attribute in which it will look for the variable and the other assumes the attribute name will be the same as the scope variable name.

angular directive with isolate scope, fields inaccessible

I'm trying to write a directive which takes a scope variable name and assigns to it the result of passing a different named parameter into a function. Below, the files="result" is intended to create a {{result}} variable in the glob isolate scope. The contents of the "matching" variable are to be evaluated in the parent context, and assigned to an isolate 'matching' variable.
the directive then calls a function eventually assigning to the isolate variable pointed to by files (result here) the array returned. expansion of {{result}} could then be used for example in an ng-repeat.
The directive should be reusable without changing the variable names.
This isn't happening. If I assign everything to a parent, I can get it working but need to change the variable names each time.
angular.module('j20-glob', ['api'])
/*
* usage: <glob files="result" matching="/bin/{{prefix}}*">
* {{result}}
* </glob>
* should allow another just after the first without stomping result
* <glob files="result" matching="/something">{{result}}</glob>
*/
.directive('glob', ['$parse', 'api', function($parse, $api) {
return {
priority: 99, // it needs to run after the attributes are interpolated
restrict: 'AE',
scope: {
},
link: function(scope, iElement, iAttributes) {
var indexModel = $parse(iAttributes.files);
iAttributes.$observe('matching', function(value) {
if (!value)
return;
$api.glob(value).then(function(res) {
indexModel.assign(scope, res);
// indexModel.assign(scope.$parent, res);
});
});
}
}
}
]);
If I understand your code here, you are having a similar issue to what I answered here: Directive doesn't work when I which the version of Angular to 1.0.1 to 1.2.27.
You have created an Element Directive, called glob. The Directive has an Isolate Scope, which you attach a property, result in your example. This all works fine. The problem is, the property in the isolate scope is only accessible within the directive; and in your case, you are trying to access it outside the directive.
The Element <glob></glob> is your directive. This Element can be containers for other Elements, for example an angular expression {{result}} but these Elements are not part of the Directive, and therefore not scoped in the isolate.
If you were to include a template, and place {{result}} inside the template, you would see the expected result. However, this stops working if you change the variable you are passing in.
A rough draft of a working Directive using a transclude function might be something like:
.directive('glob', ['$parse', 'api', function($parse, $api) {
return {
priority: 99, // it needs to run after the attributes are interpolated
restrict: 'AE',
scope: {
},
transclude : true,
link: function(scope, iElement, iAttributes, ctrl, transclude) {
var indexModel = $parse(iAttributes.files);
iAttributes.$observe('matching', function(value) {
if (!value)
return;
$api.glob(value).then(function(res) {
indexModel.assign(scope, res);
// indexModel.assign(scope.$parent, res);
});
//append our scope into the DOM element (clone) instead of $scope
transclude(scope, function(clone, scope){
element.append(clone);
});
});
}
}
}
]);

Scope variables are not binded in directive for jQuery plugin

I'm trying to bind 2 values from an input field to a scope variable. First is an input's value (color, written as text), and the second one is an attribute value (opacity value). I want them to change, and, as well, I want so their value be outputted.
myApp.directive( 'watchOpacity', function() {
return {
restrict: 'A',
link: function( scope, element, attributes ) {
scope.$watch( attributes.opacity, function(value) {
console.log( 'opacity changed to', value );
});
}
};
})
Plunker demo
The problem is that neither the input's value, nor the attribute's value is displayed/binded.
Use the change callback jQuery MiniColors provides. It will have hex and opacity passed in as arguments, which you can use to set your scope.data properties.
You need to wrap the setting of those properties in a scope.$apply callback to ensure a digest cycle is run afterwards, so that your view is updated:
.directive( 'watchOpacity', function($timeout) {
return {
restrict: 'A',
require: 'ngModel',
scope: {
watchOpacity: '='
},
link: function( scope, element, attributes, ngModel ) {
$timeout(function(){
element.attr('data-opacity', scope.watchOpacity);
$(element).minicolors({
opacity: true,
defaultValue: ngModel.$modelValue || '',
change: function(hex, opacity) {
ngModel.$setViewValue(hex);
scope.$apply(function() {
scope.watchOpacity = opacity;
})
}
});
});
}
}
})
Using this directive, your view would look like this (ng-init is optional depending upon whether or not you require default values or if you've placed them in the controller):
<input type="text" watch-opacity="data2.opacity" ng-model="data2.color"
ng-init="data2.color = '#0000FF'; data2.opacity = 0.5;" />
Working fork of your demo
Firstly : You've defined data-opacity in your element , while you want to use it as attributes.opacity , which is not even logically correct
Secondly : in your directive , if you want to read the value of your current directive , I mean this :
watch-opacity="data.opacity"
You need to say something like this in your directive :
link:function(scope,element,attributes){
var data_opacity = attributes.watchOpacity// you have written attribute.opacity!
}
I dont know what you are going to do :(

Angular directive doesn't read attribute

I have a simple Angular directive that adds a CSS class to an element when being clicked:
.directive("addClass", function () {
return {
scope: {
name: "=addClass"
},
link: function (scope, element, attributes) {
element.on("click", function () {
element.addClass(scope.name);
console.log("Element activated");
});
element.on("mouseleave", function () {
element.removeClass(scope.name);
console.log("Element deactivated");
});
}
}
});
I'm using it on my links:
...
But when I click my link my event handlers execute, although scope.name is undefined. I could read attribute value using attributes, but that beats the purpose of assigning attribute values to scope properties as described in Angular Directives API.
What am I doing wrong?
Replace =addClass with #addClass, or, in case you don't need an isolate scope, just read the class name right out of the attribute object:
element.on("click", function() {
element.addClass(attributes.addClass);
...
});
The reason = doesn't work in your case is that it expects a property so a two-way binding can be established and you're providing a static string (I'm assuming you are since class-name isn't a valid property name).
try ..., note the single-quoted 'class-name'

how to set an interpolated value in angular directive?

How do I set the interpolated value in a directive? I can read the correct value from the following code, but I have not been able to set it.
js:
app.directive('ngMyDirective', function () {
return function(scope, element, attrs) {
console.log(scope.$eval(attrs.ngMyDirective));
//set the interpolated attrs.ngMyDirective value somehow!!!
}
});
html:
<div ng-my-directive="myscopevalue"></div>
where myscopevalue is a value on my controller's scope.
Whenever a directive does not use an isolate scope and you specify a scope property using an attribute, and you want to change that property's value, I suggest using $parse. (I think the syntax is nicer than $eval's.)
app.directive('ngMyDirective', function ($parse) {
return function(scope, element, attrs) {
var model = $parse(attrs.ngMyDirective);
console.log(model(scope));
model.assign(scope,'Anton');
console.log(model(scope));
}
});
fiddle
$parse works whether or not the attribute contains a dot.
If you want to set a value on the scope but don't know the name of the property (ahead of time), you can use object[property] syntax:
scope[attrs.myNgDirective] = 'newValue';
If the string in the attribute contains a dot (e.g. myObject.myProperty), this won't work; you can use $eval to do an assignment:
// like calling "myscopevalue = 'newValue'"
scope.$eval(attrs.myNgDirective + " = 'newValue'");
[Update: You should really use $parse instead of $eval. See Mark's answer.]
If you're using an isolate scope, you can use the = annotation:
app.directive('ngMyDirective', function () {
return {
scope: {
theValue: '=ngMyDirective'
},
link: function(scope, element, attrs) {
// will automatically change parent scope value
// associated by the variable name given to `attrs.ngMyDirective`
scope.theValue = 'newValue';
}
}
});
You can see an example of this in this Angular/jQuery color picker JSFiddle example, where assigning to scope.color inside the directive automatically updates the variable passed into the directive on the controller's scope.

Resources