I have a custom directive that I would like to inherit the parent scope. I would also like to pass a value via an attribute. It looks like this:
Controller
app.controller('Main', ['$scope', function($scope){
$scope.cols = { 'col1': true, 'col2':false, 'col3': true};
$scope.toggleCol = function(colName){
$scope.cols[colName] = !$scope.cols[colName];
};
}]);
Directive
wrApp.directive("custTh", function(){
return {
restrict: "EA",
scope: false,
replace: true,
template: '<th ng-show="cols[{{ colname }}]" ng-click="toggleCol({{ colname }})">{{ colname }}</th>',
};
});
HTML
<th cust-th colname="col2"></th>
I just can't seem to get access to the attribute value bc I am inheriting the parent scope. Is it possible to directly access directive attributes from the template?
You can inherit the scope but also create a child scope by using:
scope: true
Then in order to pass values:
link: function(scope, element, attrs) {
scope.$watch(attrs.valueFromOutside, function(newValue) {
scope.valueFromOutside = newValue;
});
}
This way in the inner scope of the directive you can have a different value while still having access to the parent scope.
You can see it all in action here:
http://jsfiddle.net/HB7LU/15120/
As long as you don't declare an isolate scope in the directive, you can access the parent scope:
<-- cust-th template can access Main controller's scope -->
<div ng-controller="Main">
<th cust-th></th>
</div>
There are just a few syntax errors in your code that are preventing you from doing this. In your template, you do not need to interpolate the values being passed into the angular directives (ng-click and ng-show):
<th ng-show="cols[colname]" ng-click="toggleCol(colname)">{{ colname }}</th>
colname has not been declared as a scope variable in your controller, so when Angular goes to compile your HTML, it will not recognize it. If you would like to continue passing it as an attribute on your HTML element, you'll need to create an angular element in your directive in order to access the value using a link function (See https://docs.angularjs.org/guide/directive). If you want to use interpolation ( {{colname}} ), then you will need to have a variable in your controller like
$scope.colname = 'col1';
No. You can't access directive attributes while you inherit parent scope. To do this you have to create directive's own scope like below:
app.directive("custTh", function(){
return {
restrict: "EA",
scope: { colname: '#'},
template: 'Name : {{ colname }}',
};
});
and in yout HTML remplate you have to write like this:
<div ng-controller="Main">
<cust-th colname="col2" ></cust-th>
</div>
I have also created a fiddle for you help. I you find this useful then please accept this as an answer.
http://jsfiddle.net/HB7LU/10806/
A template can be a string or a function:
template
HTML markup that may:
Replace the contents of the directive's element (default). Replace the
directive's element itself (if replace is true - DEPRECATED). Wrap the
contents of the directive's element (if transclude is true). Value may
be:
A string. For example <div red-on-hover>{{delete_str}}</div>.
A function which takes two arguments tElement and tAttrs (described in
the compile function api below) and returns a string value.
See example at use attribute in template
See document at Angular Doc
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
I have a directive where a list(array) of items is passed in through the scope of the controller into the scope of the directive whereby the template of the directive then has access to the items.
I would like to have it so that the list of items is passed to the directive (where it is then used within the link function) and then not directly accessible through the directive's template.
i.e. if we had the following directive:
directive('itemList', function(){
return {
scope: {
items: '='
}
link: function(scope, elem, attrs){
var item-list = scope.items;
// ... want to manipulate first ...
}
}
})
the variable scope.items is now available to any template that the directive uses. Whereas I don't want that to be the case and would like to pass in something to the directive without making it known to the scope. Is this possible?
Since the directive scope pretty much by definition is the scope used by the directive's template, I don't see any obvious way of doing this in a strict information hiding way. But, why not just use a different name for the passed scope variable and what the template binds to? For example, if you said scope: { myPrivatePassedItems: '=items' }, you can now work with scope.myPrivatePassedItems as much as needed before setting it as scope.items. With this approach, the HTML in both the usage of the directive and the directive's template just sees "items", but internally your directive has its own "private" variable.
I should add that the above is the simple change needed for the one-way data flow from the consumer to the directive template. If you also need to update the original items array, you will also want to add a scope.$watch on the scope.items array after you have done your initial setup. You would then need to carry those changes (possibly with modifications) back to scope.myPrivatePassedItems in order to update the consumer's array.
You can use the $parse service to retrieve the value without using scope: {}, but you will lose the 2 way data binding that you inherently get from using scope: { items: '=' }. As mentioned by Dana Cartwright, if you need 2 way binding, you have to set this up manually with watches.
directive('itemList', function($parse){
return {
link: function(scope, elem, attrs){
var itemList = $parse(attrs['items'])(scope);
// do stuff with itemList
// ...
// then expose it
scope.itemList = itemList;
}
};
});
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.
I'm a new user of AngularJS and I'm getting stuck when I have to access to a variable in a controller from a directive inside a ng-repeat. I think it has to be something related to scopes.
Here's my fiddle: http://jsfiddle.net/Zzb58/1/
The 'MyCtrl' scope has two properties: "items", an array, and "thing", just a string.
function MyCtrl($scope) {
$scope.items = [{id: 'item1'}, {id: 'item2'}];
$scope.thing = "thing";
}
So, if I create a directive and I want it to read the array's content, this one works perfectly:
<div my-directive ng-repeat="item in items" item='item'></div>
app.directive('myDirective', function () {
return {
restrict: 'AE',
scope: {
item: '=item',
},
template: '<div>{{ item.id }}</div>'
}
});
The HTML is refreshed properly.
But if I try to access to the "thing" variable from the directive, it is always read as "undefined".
app.directive('myDirective', function () {
return {
restrict: 'AE',
scope: {
item: '=item',
thing: '='
},
template: '<div>{{ item.id }} - Here is not the {{thing}}</div>'
}
});
I think the problem has to be related to the scope child created in the ng-repeat, or maybe the variable is not being bound correctly.
However, If I use $parent.thing in the template, the variable is read and it's evaluated properly, but I don't know if that $parent is the ng-repeat scope or 'MyCtrl' scope.
1) What am I doing wrong?
2) Why is it needed to put the "item" element read in the ng-repeat as an attribute of the HTML element? I first thought "items" was in the parent scope, so when the isolated scope of the directive is created, I would just have to do something like "items: '='".
By assigning an object to it, you create an isolate scope. If you set scope : true, you can use it like in your example, if not you have to pass it as an attribute, like you did with item.
http://jsfiddle.net/Zzb58/2/
For an explanation of different modes of scope, you can read the directives section on this page:
https://github.com/angular/angular.js/wiki/Understanding-Scopes
Generally but especially when working with inherited scope values, I would also highly recommend using an object with a property set, instead of a simple string-variable, as this might lead to a detachment throughout the scopes due to how prototypical inheritance works.
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.