AngularJs directives - how to get attributes values from within directive - angularjs

Any idea how to access attributes values from inside the directive?
angular.module('portal.directives', [])
.directive('languageFlag', ['$routeParams', function(params) {
return function(scope, element, attrs) {
console.log(attrs["data-key"]); // returns undefined
console.log(element.attr('data-key')); // returns {{data-key}}
angular.element(element).css('border', '1px solid red');
};
}]);
Html code is:
<ul>
<li ng-repeat="lng in flags">
<a class="lngFlag {{flag.Key}}" data-key="{{flag.Key}}" data-id="{{lng.Id}}" ng-click="changeLangHash({'lng':lng.Id })" language-flag></a>
</li>
</ul>
Thanks

Use $observe:
Observing interpolated attributes: Use $observe to observe the value changes of attributes that contain interpolation (e.g. src="{{bar}}"). Not only is this very efficient but it's also the only way to easily get the actual value because during the linking phase the interpolation hasn't been evaluated yet and so the value is at this time set to undefined. -- directives doc
return function(scope, element, attrs) {
attrs.$observe('key', function(value) {
console.log('key=', value);
});
}
As #FMM mentioned in a comment, data is stripped by Angular when it normalizes the attribute name, hence the use of key above, rather than dataKey.

try attrs["dataKey"] - this is the way that html parses attributes with dash (-).
if you want the value from the scope instead of {{something}}, you can do two things:
scope[attrs['dataKey']] - will work but shouldn't do this
or use $parse but then don't use ``{{}}`
app.directive('languageFlag', ['$routeParams','$parse', function(params,$parse) {
return function(scope, element, attrs) {
var value = $parse(attrs.dataKey)(scope);
console.log(value);
angular.element(element).css('border', '1px solid red');
};
}]);
or you can use $interpolate the same way like $parse but with {{}}

angular strips 'data-' off any attributes, so if your attribute is 'data-key', just use 'key', not 'dataKey'

I would suggest using object notation if you are inside the link function of the directive, which gets the attrs parameter:
attrs.yourAttributeName

Another issue I discovered is that $attr will convert attribute names to lower-casing.
<input myTest="test" />
Value can be obtained with this... attr["mytest"]
i.e.
...link: function (scope, element, attr) { console.log(attr["mytest"]); ...}

Related

Get string value from AngularJS directive tag: my-first-directive="I want this"

I am wanting to pass a string value to my AngularJS directive without using a separate attribute, something like this...
In my HTML
<div my-first-directive="number 1"></div>
<div my-first-directive="number 2"></div>
<div my-first-directive="number 3"></div>
and in my JavaScript
.directive('myFirstDirective', function () {
'use strict';
return {
restrict: 'A',
link: function (scope, element) {
// now I want the string that follows the directive
console.log(element[0].attributes[0].nodeValue);
console.log(element[0].attributes[0].textContent);
console.log(element[0].attributes[0].value);
}
};
});
Now all the three console.log methods output the string I require... however I am unsure this isn't the best way to obtain such a value, don't I need to think about isolate scope and the like? I don't require "2 way binding" or anything. Is there a better or AngularJS way of obtaining the string?
Many thanks in advance
link() takes an attrs argument, you will find what you want there:
link: function (scope, element, attrs) {
// what you want is:
console.log(attrs.myFirstDirective);
}
The attribute names are normalized.

Is it possible to conditionally apply transclution to directive?

Is it possible to decide whether to apply transclusion to an element based on a scope variable ?
For example ( Stupid simplified reduced example of what i'm trying to achieve )
app.directive('myHighlight', function () {
return {
transclude : true,
template : "<div style='border:1px solid red'><span ng-transclude></span></div>"
}
});
app.directive('myDirective', function () {
return {
template : "<span>some text</span>",
link : function (scope,element,attr) {
if ( 'shouldHighlight' in attr) {
// wrap this directive with my-highlight
}
}
}
});
And then in the html
<span my-directive></span>
<span my-directive should-highlight></span>
Note, please don't tell me to just add the highlight instead of should-highlight, as i said this is a dumb reduced example. Thanks.
Instead of optionally applying the highlight directive, always apply it and do the optional wrapping inside that directive. The optional wrapping is achieved with an ng-if and a boolean passed from myDirective to myHighlight via markup:
<div my-highlight="someBooleanValue">some text</div>
The myHighlight template:
<div ng-if="actuallyTransclude" style="border:1px solid red">
<span ng-transclude></span>
</div>
<div ng-if="!actuallyTransclude" ng-transclude></div>
Working jsfiddle: http://jsfiddle.net/wilsonjonash/X6eB5/
Sure. When you specify the transclude option, you know that you can declaratively indicate where the content should go using ng-transclude.
In the linking function of the directive, you will also get a reference to a transclude function (https://docs.angularjs.org/api/ng/service/$compile, see link section):
function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
The transcludeFn will return the transcluded content, so you can conditionally insert that were and when you want to in the link function of your directive.
Example (http://jsfiddle.net/DKLY9/22/)
HTML
<parentdir flg="1">
Child Content
</parentdir>
JS
app.directive('parentdir', function(){
return {
restrict : 'AE',
scope: {
flg : "="
},
transclude : true,
template : "<div>Parent {{childContent}} Content</div>",
link : function(scope, elem, attr, ctrl, transcludeFn){
if (scope.flg==1){
scope.childContent="Include Me instead";
}
else {
scope.childContent = transcludeFn()[0].textContent;
}
}
}
});
This is a simplified example. To get a better idea of how to use the transclude function, refer to the following : http://blog.omkarpatil.com/2012/11/transclude-in-angularjs.html
When I approach these kind of problems I just look at what angular did. Usually their source code is very readable and easy to re-use. ngTransclude is no different:
https://github.com/angular/angular.js/blob/master/src/ng/directive/ngTransclude.js
I leave the rest to you. You can either create your own transclusion directive that receives also a condition, or just duplicate the code into your specific directive when the if condition is true.
If you still have trouble, please let me know and we'll set up a plunker.

How can I pass a model into custom directive

My goal is to pass the projectName model from my MainController to my custom contenteditable directive.
I have the following controller:
app.controller("MainController", function($scope){
$scope.projectName = "Hot Air Balloon";
});
Here is how I'm calling the directive:
<div class="column" ng-controller="MainController">
<h2 contenteditable name="myWidget" ng-model="projectName" strip-br="true"></h2>
<p>{{projectName}}</p>
</div>
I've gotten the contenteditable directive working by following this tutorial: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
If I understand the docs correctly then Angular is not going to use the model I want it to. Instead its going to create a new model w/ scope local to the contenteditable directive. I know that I can add an isolate scope to the directive but I don't know how to use the model passed to the isolate scope within the link function.
I have tried something like the following which didn't work ...
<h2 contenteditable item="projectName"></h2>
--- directive code ---
scope: {
item: '=item'
}
link: function(){
...
item.$setViewValue(...)
...
}
--- my original directive call --
<div class="column" ng-controller="MainController">
<h2 contenteditable name="myWidget" ng-model="projectName" strip-br="true"></h2>
<p>{{projectName}}</p>
</div>
--- my original controller and directive ---
app.controller("MainController", function($scope){
$scope.projectName = "LifeSeeds";
});
app.directive('contenteditable', function(){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel){
console.log(ngModel);
if(!ngModel) return;
console.log(ngModel.$viewValue);
ngModel.$render = function(){
element.html(ngModel.$viewValue || '');
};
element.on('blur keyup change', function(){
scope.$apply(read);
});
read();
function read(){
var html = element.html();
if(attrs.stripBr && html == '<br>'){
html = '';
}
ngModel.$setViewValue(html);
}
}
};
});
You can use ng-model with your own directive. To make sure it is included, you can use the attribute require like this:
app.directive("myDirective", function(){
return {
require:"ngModel",
link: function(scope, element, attr, ngModel){
console.log(ngModel);
}
}
});
Then, you can code whatever behavior your want of ng-model within your directive.
Working solution: http://plnkr.co/edit/Lu1ZG9Lpx2sl8CYe8FCx?p=preview
I mentioned that I tried using an isolate scope in my original post.
<h2 contenteditable item="projectName"></h2>
This was actually the correct approach so ignore my full original example using the model argument of the directive's link function.
link: function(scope, element, attrs, ngModel)
The reason the isolate scope approach did not work was because $scope.projectName stored a primitive instead of an object. I did not understand some javascript basics. Mainly, I did not know that primitive types were passed to functions by value.
Primitives are passed by value in javascript. Consequently, changes made to primitive values within a function do NOT change the value of the variable passed to the function.
function changeX(x){
x = 5;
}
x = 4;
changeX(x);
console.log(x) // will log 4 ... Not 5
However, objects passed to functions in javascript are passed by reference so modifications to them within the function WILL be made to the variable passed into the function.
My problem was in how I declared the scope within the MainController.
I had:
$scope.projectName = "LifeSeeds";
That's a primitive. When I passed projectName to the directive I was passing a primitive.
<h2 contenteditable item="projectName"></h2>
Thus, changes to the editable element were being made to the value within the directive but not to the value stored in the MainController's scope. The solution is to store the value within an object in the MainController's scope.
// correct
$scope.project = {
html: "Editable Content"
};
// wrong
$scope.projectName = "Editable Content"

Obtaining element attributes within a custom directive used within ng-repeat

This is driving me a little bit crazy. I need to read the src of an image within a custom attribute:
app.directive('imgTransform', function () {
return {
retrict: 'A',
link: function (scope, elem, attrs) {
console.log(elem.attr('ng-src'));
}
}
});
This works fine when used like so:
<img ng-src='http://lorempixel.com/100/100/technics' alt="" img-transform="" />
However, it does not work inside ng-repeat:
<p ng-repeat='image in images'>
<img ng-src='{{image}}' alt="" img-transform="" />
</p>
The value returned is {{image}}. How do I get the actual value?
Try using the attrs:
console.log(attrs.ngSrc);
Fiddle: http://jsfiddle.net/6SuWD/
The reason for this could be that ng-repeat uses the original DOM as template and recreates it for each iteration. For some (obscure to me) reason, you are reading the attribute of the template. This explanation could be very wrong though...
However, since Angular gives you the API to access the attributes, it would be safer to go with it anyway.
You will have to watch for changes in this attribute using $observe since ng-repeat interpolates the values of ng-src. See this reference.
LukaszBachman is correct. When you pass interpolated values to a directive, the interpolation hasn't fired yet when the directive is in its linking phase.
If you were to do console.log(attrs); - you would clearly see that there is an actual value on ngSrc, when looking in the browser console. However, since interpolation hasn't kicked in yet you dont have access to it.
This would get you the actual value of ngSrc:
myApp.directive('imgTransform', function () {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
attrs.$observe('ngSrc', function (val) {
console.log(val);
});
}
}
});

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