AngularJS Directive: Pass dynamically created object as attribute for nested directive - javascript

I have a directive that wraps around another directive . The child directive accepts an "options" object as an attribute. I want to create this options object in the parent directive's link function and then set it as an attribute on the child directive in the parent's template, but the options object does not get set if its created dynamically. This works if the options object is set statically in the template itself.
I have a plunker here: http://plnkr.co/edit/gNeKMcneO8RDBmlmpt72?p=preview
Any pointers would be greatly appreciated!!
angular.module('nestedDirectives', [])
.directive('fruitinfo',
[
function() {
return {
restrict: 'A',
scope: {
fruitname: '#?'
},
template: '<br>Fruit Name: {{fruitname}}<br>Fruit Options: {{fruitoptions}}',
link: function(scope, element, attrs) {
scope.fruitoptions = scope.$eval(attrs['fruitinfo']);
}
};
}])
.directive('fruits',
[
function() {
return {
restrict: 'E',
scope: {
selectedFruits: '=?',
btnSizeClass: "#?"
},
template: 'btnSizeClass: {{btnSizeClass}}<br>Fruits: {{fruits}}<br><div ' +
' fruitinfo="fruitOptions" ' +
' fruitname="{{f}}"' +
' ng-repeat="f in fruits">' +
'</div><br><br>' +
'<div fruitname="With static fruitOptions: {{f}}" fruitinfo="{test: \'testOption\', btnSizeClass: \'btn-xs\'}" ng-repeat="f in fruits"></div>',
link: function(scope, element, attrs) {
scope.fruitOptions = {test: 'testOption', btnSizeClass: scope.btnSizeClass};
scope.fruits = ['Apple', 'Banana', 'Watermelon', 'Strawberry'];
}
};
}]
)
;

any particular reason why you are using $eval instead of using "&" in your scope definition like this
http://plnkr.co/edit/W47LZsQ3i4zS8Feu7sDl?p=preview
if you use
fruitoptions:'&fruitinfo'
and then you do
scope.fruitoptions=$scope.fruitoptions()
in your link function you'll get the evaluated expression in its original scope, also consider doing this on the controller function which is invoked prior the link cycle

I figured it out. The "fruitOptions" value must be serialized so that the template can compile it as an attribute, which can then be converted back to an object using "eval" in the nested directive. Plunker updated.

Related

How to resolve attribute values in angular directive?

I want to create a directive which picks up the template file as well as the controller based on the value of one of the attributes.
Here's my directive so far:
app.directive('myDir', function() {
return {
templateUrl: function (element, attrs) {
return "/" + attrs.targetpath + ".html";
},
restrict: 'E',
controller: '#',
name: 'targetpath'
};
});
And I use it like...
<my-dir targetpath='path'></my-dir>
I have this scope variable which stores the path like so
$scope.path = 'someWhere';
The problem is that the directive treats the targetpath attribute as a string and not as a scope variable.
So, it looks for /path.html with path controller, instead of /someWhere.html with someWhere controller
Plunk's over here

Trying to load variable into Angular directive

So I am so confused! I have HTML that looks like this:
<span paper-embed="" url="theUrl"></span>
and theUrl is a variable that loads a different URL from my ng-controller. Then I have an Angular directive looks like this:
app.directive('paperEmbed', function() {
return {
restrict: 'AEC',
transclude:true,
scope: {
key: '=',
value: '='
},
link: function(scope, element, attrs) {
// is does some jQuery here
}
};
});
my question is, I want to access the URL inside the directive - the variable named theUrl, so how do I do that? I looked up on SA and seemed like
console.log({{theUrl}});
might work but it does not.
Change your scope to
scope: {
key: '=',
value: '=',
url: '='
},
And in your link function you can use it like scop.url
You can access it with attrs.url
Since you are trying to implement a directive with the following:
<span paper-embed="" url="theUrl"></span>
you should use restrict: 'A',
the url attribute can be accessed either through your scope declaration as # or through the attrs object passed into your link function.
as to the console.log({{theUrl}}). {{}} is specifically design to interpolate javascript-keys in the markup, that is processed through the $compile cycle.
Here is something to get you started:
HTML
<span paper-embed url="http://example.com" />
javascript
var app = angular.module('exampleApp', []);
app.directive('paperEmbed', function() {
return function(scope, elem, attrs) {
console.log(attrs.url);
};
});
get a little more detailed information here: https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object
You can either use attrs.url to access it or you can add the url to the scope:
app.directive('paperEmbed', function() {
return {
restrict: 'AEC',
transclude:true,
scope: {
key: '=',
value: '=',
url: '#'
},
link: function(scope, element, attrs) {
console.log(scope.url);
}
};
Whenever you add a variable to the scope like above, the directive is expecting an html attribute to supply that value. In this case url.
<div paper-embed="obj1" key="obj2" value="obj3" url="obj4"></div>
scope.url === obj4;
Among many others, there are two easy ways to do this:
- First method is to access it using the attrs.
- Second method is to pass the parameters by defining in the scope.
First method is to use attrs. attrs has all the attributes. You can simply access the url in the link function as attrs.url
link:function(scope,element,attrs){
console.log(attrs.url)
}
You will see whatever is placed instead of theUrl is displayed on the console.
The other alternative is to change your directive to the following:
app.directive('paperEmbed', function() {
return {
restrict: 'AEC',
transclude:true,
scope: {
key: '=',
value: '=',
theUrl: '='
},
link: function(scope, element, attrs) {
// is does some jQuery here
}};});
Now you can access it in the link function as scope.url. To set the value to be passed simply set $scope.theUrl='whatever your url is' in your ng-controller.

Custom directive scope vs attrs

I have one concern when creating a custom directive in angular.
When I'm using a link function, I'm not sure what is the real difference when accessing attributes with attrs or scope.
Take this piece of code for example:
myApp.directive('someDirective', function() {
return {
restrict: 'E',
replace: true,
scope: {
title: '=title'
},
template: '<img/>',
link: function(scope, element, attrs) {
if (scope.title) {
// do something here
}
if (attrs.title){
// do something here
}
},
}
From my observations accessing 'title' attribute from attrs and by scope has a similar effect. What is the real difference?
The difference is that attribute is of a String type by definition. Always. In your case attrs.title will be literally string equal to whatever you pass into attribute in HTML.
However, scope.title is parsed and evaluated result of the attribute attr.title.
Ex. If you use something like this in HTML
<some-directive title="name"></some-directive>
where $scope.name = "Thomas Mann" defined in the scope, then attr.title will be string "name", while scope.title will be "Thomas Mann".

Angular directive scope evaluation with function binding ('&')

We are seeing some unexpected behavior in how a function is being bound to a directive scope. Here is a jsbin example.
To summarize - we have a directive that has a scope object as follows:
scope: { fn: '&', val: '#' }
The directive displays the result of fn twice. First we display the result as it is evaluated in the template, then we display the result when evaluated in the link function:
<div><code>fn (&)</code>: {{fn()}}</div>
<div><code>fn result ($scope.result = $scope.fn()) </code>: {{result}}</div>
We then use this scope in another directive:
app.directive('rootDirective', function() {
function link($scope, $elem, $attrs) {
$scope.name = 'directive with scope';
}
return {
scope: 'isolate',
replace: true,
restrict: 'E',
link: link,
template: [
'<div add-scope-directive="">',
' <div ng-repeat="n in [1]">',
' <sub-dir val="{{val}}" fn="fn()" name="{{n}}"></sub-dir>',
' </div>',
' <sub-dir val="{{val}}" fn="fn()" name="{{name}}"></sub-dir>',
'<div>'
].join('\n')
};
});
On the root node of this directive we have another directive add-scope-directive. In this directive we define fn - which returns "add-scope-directive - fn".
We would now expect to see that the result of fn ("add-scope-directive - fn") to be the same throughout the directive. However the result from the link function of the child directive 'sub-dir' when it is not used in a repeater is different - instead it is coming from the function on the MainCtrl.
The question is - are our expectations correct and is this a bug? Or should we be expecting what we see here and if so why?
Not a proper solution, but a workaround could be to put a timeout into the link function of sub-dir, like so:
setTimeout(function() {
$scope.result = $scope.fn();
$scope.$apply();
}, 0);

Call function on directive parent scope with directive scope argument

I am developing a directive which shows and hides it's contents based on a click event (ng-click) defined in it's template. On some views where the directive is used I'd like to be able to know if the directive is currently showing or hiding it's contents so I can respond to the DOM changes. The directive has isolated scope and I am trying to notify the parent scope when the directive has been "toggled". I'm attempting to accomplish this by passing a callback function to the directive where it is used that can be called when the directive's state changes i.e hides or shows
I'm not sure how to correctly implement this being that the state of the directive (hidden or shown) is stored in the directive's isolated scope and is determined after the ng-click. Therefore I need to call the parent scope's function from within the directive and not from withing the view.
This will make WAAY more sense with an example. Here is a plunked demonstrating what I'd like to do:
http://plnkr.co/edit/hHwwxjssOKiphTSO1VIS?p=info
var app = angular.module('main-module',[])
app.controller('MainController', function($scope){
$scope.myValue = 'test value';
$scope.parentToggle = function(value){
$scope.myValue = value;
};
});
app.directive('toggle', function(){
return {
restrict: 'A',
template: '<a ng-click="toggle();">Click Me</a>',
replace: true,
scope: {
OnToggle: '&'
},
link: function($scope, elem, attrs, controller) {
$scope.toggleValue = false;
$scope.toggle = function () {
$scope.toggleValue = !$scope.toggleValue;
$scope.OnToggle($scope.toggleValue)
};
}
};
});
I'm relatively new to Angular. Is this a bad idea to begin with? Should I be using a service or something rather than passing around function refs?
Thanks!
Update
You can also use & to bind the function of the root scope (that is actually the purpose of &).
To do so the directive needs to be slightly changed:
app.directive('toggle', function(){
return {
restrict: 'A',
template: '<a ng-click="f()">Click Me</a>',
replace: true,
scope: {
toggle: '&'
},
controller: function($scope) {
$scope.toggleValue = false;
$scope.f = function() {
$scope.toggleValue = !$scope.toggleValue;
$scope.toggle({message: $scope.toggleValue});
};
}
};
});
You can use like this:
<div toggle="parentToggle(message)"></div>
Plunk
You could bind the function using =. In addition ensure the property name in your scope and tag are matching (AngularJS translates CamelCase to dash notation).
Before:
scope: {
OnToggle: '&'
}
After:
scope: {
onToggle: '='
}
Furthermore don't use on-toggle="parentToggle({value: toggleValue})" in your main template. You do not want to call the function but just passing a pointer of the function to the directive.
Plunk

Categories