I have a directive which I would like to be able to load a different template on some logic.
In the example below, the 'type' is a variable on the scope which I would like to pass to the directive to build the URL for the directive's template.
<direct type="{{type}}"></direct>
var direct = function () {
return {
restrict: 'E',
templateUrl: function(tElement, tAttrs) {
console.log(type);
return 'resources/' + tAttrs.type + '.html';
}
};
};
The type is not being evaluated but instead the actual string 'type' is being passed in the tAttrs. Do you know what I might be missing out?
You have to do this via isolated scope on your direct directive.
I have made an example for you here: http://jsbin.com/lagez/1/edit
You can see in the example link, that you have to specify attributes that you would like to read inside your directive scope inside the scope: { attributeName: '#' }
The attribute name syntax inside scope: {} should be camel case with first letter small. e.g. typeName, and you specify the attribute-name inside the directive like
The problem you're running into is that when templateUrl is evaluated, there's no scope to bind that value to, so it returns the actual string content of the attribute--rather than the interpolated value you expect.
Related
I have a directive whose 'config' attribute value I need to access inside my directive controller.
Since the controller constructor get executed first,communication from controller to link is possible but not vice versa.
What should be the best way to achieve this?
I have considered the following approaches
1)Add the variable to scope-
That would in my opinion pollute the scope,making the variable accessible every where else where the scope is being shared.
2)Use $broadcast
Again the same issue as above
3) Pass a callback function on controller's this and call it from the link function with config as its argument
4)Pass the value through a service- In my case I have multiple such directives that would need to pass date through this service
Or is there some better approach that I am missing out for doing this?
module.directive('myDirective',function(){
return{
restrict:'E',
templateUrl:'path/to/html',
link:function(scope,iElement,iAttrs,controller){
var config=iAttrs.config;
//How to access this value inside the directive controller?
},
controller:function($scope){
//the directive attribute 'config' is required here for some larger computations which are not
//manipulating the DOM and hence should be seperated from the link function
})
There you can use isolated scope concept where you create isolated scope inside your controller & that would not be prototypically inherited from its parent scope. For that you need to use scope: { ... } inside your directive option. There are three options to pass scope value inside a directive through attribute
# : One way binding
= : Two way binding
& : Expression
In your case first two cases would be fine that are depends which one you need to use. If you just want to pass the value of scope variable to the directive in that case you could use 1st approach which would be # one way binding.
If you want to update the variable in both directive as well as controller from where it come i.e. nothing but two way binding, then you need to use =
I think = suits in your case so you should go for =
Markup
<my-directive config="config"></my-directive>
Directive
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
config: '='
},
templateUrl: 'path/to/abc.html',
link: function(scope, iElement, iAttrs, controller) {
//here it will be access as scope.config
console.log(scope.config);
},
controller: function($scope) {
console.log($scope.config); //here also it would be available inisde scope
//you could put a watch to detect a changes on config
}
}
});
Demo Plunkr
Update
As config value has been provide from the attribute with expression like {{}} so we could get those changes inside controller by putting [**$observe**][2] on $attrs. For that you need to inject $attrs dependency on your controller that will give you all the attributes collection which are available on directive element. And on the same $attrs object we gonna put $observe which work same as that of $watch which does dirty checking & if value gets change it fires that watch.
Directive
app.directive('myDirective', function() {
return {
restrict: 'E',
templateUrl: 'path/to/abc.html',
link: function(scope, iElement, iAttrs, controller) {
//here it will be access as scope.config
console.log(scope.config);
},
controller: function($scope,$attrs) {
//you could put a watch to detect a changes on config
$attrs.$observe('config', function(newV){
console.log(newV);
})
}
}
});
Updated Demo
Alright, so I would like to use a scope variable in a dynamically generated template url. So I tried this:
html
<my-directive type="{{ type }}"></my-directive>
js
angular.module('myApp', [])
.directive('myDirective', function () {
return {
templateUrl: function (tElement, tAttrs) {
return 'templates/myDirective.' + tAttrs.type + '.html';
};
};
});
I was expecting tAttrs.type to return the value of $scope.type, but instead I ended up getting {{ type }}. This resulted in a templateUrl of templates/myDirective.{{ type }}.html.
So, what can I do to get the scope variable's value instead of the raw text?
Scope values are not accessible from within a directive's templateUrl. The attributes are not compiled yet, so from within this context it is not possible to access the scope.
Here is a workaround that might work for you.
See Plunkr
What I did here is using a template containing a div with ng-include, getting the url through a two-way bind.
I'm confused as to why this isn't working...
here is my directive:
app.directive('dateDropDowns', function () {
function link($scope, elem, attr) {
console.log($scope.startYear);
};
function controller($scope, $attrs, $location) {
};
return {
link: link,
scope: {
startYear: "&",
endYear: "&",
date: "=",
required: "&"
},
restrict: "EA",
templateUrl: "/scripts/templates/datedropdowns.html",
controller: controller
}
});
Here is my directive html:
<div date-drop-downs start-year="startYear" end-year="endYear" required="true" date="testDate"></div>
startYear, endYear, testDate are all defined in the scope of the controller on which the above html exists.
The console.log in the link function is returning:
function (locals) {
return parentGet(scope, locals);
}
However, the startYear should be returning 1910 as that is what is set to in the controller on which the "date-drop-downs" directive is being called.
I've tried console.log($scope.$eval($scope.startYear)); but it returns the same thing above
I've tried console.log($scope.$eval(attr.startYear)); but it returns the same thing above
I've tried console.log(attr.startYear); but that just returns the text "startYear"
I'm not sure what to do to actually get the startYear value.
Any help is greatly appreciated.
Since you are using & binding they get wrapped in as function if they are already not function. This is generally used to invoke a method on the parent using isolate scope.
So you should actually do:-
$scope.startYear()
to get the value of startYear, if you are looking for 2 waybinding you should go with = only, if you just need it as text binding then use # and set the value as start-year="{{startYear}}" on the directive
& or &attr - provides a way to execute an expression in the context of the parent scope. If no attr name is specified then the attribute name is assumed to be the same as the local name. Given and widget definition of scope: { localFn:'&myAttr' }, then isolate scope property localFn will point to a function wrapper for the count = count + value expression. Often it's desirable to pass data from the isolated scope via an expression to the parent scope, this can be done by passing a map of local variable names and values into the expression wrapper fn. For example, if the expression is increment(amount) then we can specify the amount value by calling the localFn as localFn({amount: 22}).
Inorder to achieve one-time binding you could use bindonce or with angular 1.3beta you could use one-time binding syntax:-
.. start-year="{{::startYear}}" ...
and
startYear: "#",
Plnkr
I have a form directive that uses a specified callback attribute with an isolate scope:
scope: { callback: '&' }
It sits inside an ng-repeat so the expression I pass in includes the id of the object as an argument to the callback function:
<directive ng-repeat = "item in stuff" callback = "callback(item.id)"/>
When I've finished with the directive, it calls $scope.callback() from its controller function. For most cases this is fine, and it's all I want to do, but sometimes I'd like to add another argument from inside the directive itself.
Is there an angular expression that would allow this: $scope.callback(arg2), resulting in callback being called with arguments = [item.id, arg2]?
If not, what is the neatest way to do this?
I've found that this works:
<directive
ng-repeat = "item in stuff"
callback = "callback"
callback-arg="item.id"/>
With
scope { callback: '=', callbackArg: '=' }
and the directive calling
$scope.callback.apply(null, [$scope.callbackArg].concat([arg2, arg3]) );
But I don't think it's particularly neat and it involves puting extra stuff in the isolate scope.
Is there a better way?
Plunker playground here (have the console open).
If you declare your callback as mentioned by #lex82 like
callback = "callback(item.id, arg2)"
You can call the callback method in the directive scope with object map and it would do the binding correctly. Like
scope.callback({arg2:"some value"});
without requiring for $parse. See my fiddle(console log) http://jsfiddle.net/k7czc/2/
Update: There is a small example of this in the documentation:
& or &attr - provides a way to execute an expression in the context of
the parent scope. If no attr name is specified then the attribute name
is assumed to be the same as the local name. Given and widget definition of scope: {
localFn:'&myAttr' }, then isolate scope property localFn will point to
a function wrapper for the count = count + value expression. Often
it's desirable to pass data from the isolated scope via an expression
and to the parent scope, this can be done by passing a map of local
variable names and values into the expression wrapper fn. For example,
if the expression is increment(amount) then we can specify the amount
value by calling the localFn as localFn({amount: 22}).
Nothing wrong with the other answers, but I use the following technique when passing functions in a directive attribute.
Leave off the parenthesis when including the directive in your html:
<my-directive callback="someFunction" />
Then "unwrap" the function in your directive's link or controller. here is an example:
app.directive("myDirective", function() {
return {
restrict: "E",
scope: {
callback: "&"
},
template: "<div ng-click='callback(data)'></div>", // call function this way...
link: function(scope, element, attrs) {
// unwrap the function
scope.callback = scope.callback();
scope.data = "data from somewhere";
element.bind("click",function() {
scope.$apply(function() {
callback(data); // ...or this way
});
});
}
}
}]);
The "unwrapping" step allows the function to be called using a more natural syntax. It also ensures that the directive works properly even when nested within other directives that may pass the function. If you did not do the unwrapping, then if you have a scenario like this:
<outer-directive callback="someFunction" >
<middle-directive callback="callback" >
<inner-directive callback="callback" />
</middle-directive>
</outer-directive>
Then you would end up with something like this in your inner-directive:
callback()()()(data);
Which would fail in other nesting scenarios.
I adapted this technique from an excellent article by Dan Wahlin at http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters
I added the unwrapping step to make calling the function more natural and to solve for the nesting issue which I had encountered in a project.
In directive (myDirective):
...
directive.scope = {
boundFunction: '&',
model: '=',
};
...
return directive;
In directive template:
<div
data-ng-repeat="item in model"
data-ng-click='boundFunction({param: item})'>
{{item.myValue}}
</div>
In source:
<my-directive
model='myData'
bound-function='myFunction(param)'>
</my-directive>
...where myFunction is defined in the controller.
Note that param in the directive template binds neatly to param in the source, and is set to item.
To call from within the link property of a directive ("inside" of it), use a very similar approach:
...
directive.link = function(isolatedScope) {
isolatedScope.boundFunction({param: "foo"});
};
...
return directive;
Yes, there is a better way: You can use the $parse service in your directive to evaluate an expression in the context of the parent scope while binding certain identifiers in the expression to values visible only inside your directive:
$parse(attributes.callback)(scope.$parent, { arg2: yourSecondArgument });
Add this line to the link function of the directive where you can access the directive's attributes.
Your callback attribute may then be set like callback = "callback(item.id, arg2)" because arg2 is bound to yourSecondArgument by the $parse service inside the directive. Directives like ng-click let you access the click event via the $event identifier inside the expression passed to the directive by using exactly this mechanism.
Note that you do not have to make callback a member of your isolated scope with this solution.
For me following worked:
in directive declare it like this:
.directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
scope: {
myFunction: '=',
},
templateUrl: 'myDirective.html'
};
})
In directive template use it in following way:
<select ng-change="myFunction(selectedAmount)">
And then when you use the directive, pass the function like this:
<data-my-directive
data-my-function="setSelectedAmount">
</data-my-directive>
You pass the function by its declaration and it is called from directive and parameters are populated.
I've a question about a directive I want to write. Here is the jsfiddle
This is the HTML
<div ng-controller="TestCtrl">
<mydir base="{{absolute}}">
<div>{{path1}}</div>
<div>{{path2}}</div>
</mydir>
</div>
The directive, called 'mydir', will need the value of the base attribute and all the path values (there can be any number of paths defined). I don't want to inject values from the controller directly into the directive scope (they need to be independent). I have 3 questions about this (checkout the jsfiddle too!)
1) although the code works as it is now, there is an error: "TypeError: Object # has no method 'setAttribute'". Any suggestions what is wrong?
2) in the directive scope.base is 'undefined' instead of '/base/'. Can this be fixed.
3) How can I make a list of all the path values in the directive ?
You need to set an isolate scope that declares the base attribute for this directive:
scope: {
base: "#"
}
This solves (2).
Problem (1) is caused from the replace: true in cooperation with the scope above, because it creates a comment node to contain the repeated <div>s.
For (3), add paths: "=" to scope and, of ocurse, pass them from the parent controller as an array.
See forked fiddle: http://jsfiddle.net/swYAZ/1/
You're creating an new scope in your directive by setting scope: true what you want to do is:
app.directive('mydir', function ($compile, $rootScope) {
return {
replace: true,
restrict: 'E',
template: '<div ng-repeat="p in paths"> {{base}}{{p}}/{{$index}}</div>',
scope: {
base : '='
},
link: function (scope, iElement, iAttrs) {
scope.paths = ['p1', 'p2'] ;
}
}
});
This will setup a bi-directional binding between your directive's local scope and the parent's scope. If the value changes in the parent controller it will change in the directive as well.
http://jsfiddle.net/4LAjf/8/
If you want to make a robust directive to be able to replace the data to its content (3)
You can pass a reference as an attribute and display the data accordingly.
See this article, look for wrapper widget.