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.
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 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
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 am developing a widget where I want to render some messages/text one after another. I want to change the template of the message based on the type of message.
my current directive setup is as follows
directive('cusMsgText', function(){
return {
restrict: 'E',
template:function(elements, attrs){
return '<div></div>';
},
link: function($scope, iElm, iAttrs, controller) {
//add children to iElm based on msg values in $scope
}
};
});
The directive is used as follows
<div ng-repeat="(key, value) in chatUser.msg">
<data-cus-msg-text msg="value.type"></data-cus-msg-text>
</div>
Now my question are -:
Is it possible to return one of multiple strings (templates) from
template function itself based on the actual value of attribute
msg. I tried accessing attrs.msg in template function and it
return value.type.
If not then, Is it good to manipulate template under linker or I
need to move it to compile function?
To render a different template based on value.type you can use the ng-switch statement:
<div ng-switch="value.type">
<div ng-switch-when="type1">
//...template for type 1 here...
</div>
<div ng-switch-when="type2">
//...template for type 2 here...
</div>
</div>
Also, if I understood your second question: manipulation of the uncompiled directive should be done in the compile function, all the manipulation which occurs after compilation should go in the link function.
Docs for ngSwitch
EDIT: +1 to Sebastian for understanding what you wanted. However, what he is proposing is essentially reinventing the wheel, since it is essentially compiling and inserting the template manually (which is what ngSwitch does for you). Also, you can access the attributes you put on your directive through the attrs argument of the link function.
In the template function you don't have access to the scope of your directive. If you want to control what gets rendered you can do this using conditional logic (e.g. ng-switch) in a global template as suggested by simoned or use a link function:
.directive('cusMsgText', function($compile) {
return {
restrict: 'E',
scope: {
msg: '=',
item: '='
},
link: function(scope, element, attrs) {
templates = {
x: '<div>template x {{item.name}}</div>',
y: '<div>template y {{item.name}}</div>'
};
var html = templates[scope.msg];
element.replaceWith($compile(html)(scope));
}
};
});
I have a directive which looks something like:
var myApp = angular.module('myApp',[])
.directive("test", function() {
return {
template: '<button ng-click="setValue()">Set value</button>',
require: 'ngModel',
link: function(scope, iElement, iAttrs, ngModel) {
scope.setValue = function(){
ngModel.$setViewValue(iAttrs.setTo);
}
}
};
});
The problem is that if I use this directive multiple times in a page then setValue only gets called on the last declared directive. The obvious solution is to isolate the scope using scope: {} but then the ngModel isn't accessible outside the directive.
Here is a JSFiddle of my code: http://jsfiddle.net/kMybm/3/
For this scenario ngModel probably isn't the right solution. That's mostly for binding values to forms to doing things like marking them dirty and validation...
Here you could just use a two way binding from an isolated scope, like so:
app.directive('test', function() {
return {
restrict: 'E',
scope: {
target: '=target',
setTo: '#setTo'
},
template: '<button ng-click="setValue()">Set value</button>',
controller: function($scope) {
$scope.setValue = function() {
$scope.target = $scope.setTo;
};
//HACK: to get rid of strange behavior mentioned in comments
$scope.$watch('target',function(){});
}
};
});
All you need to do is add scope: true to your directive hash. That makes a new inheriting child scope for each instance of your directive, instead of continually overwriting "setValue" on whatever scope is already in play.
And you're right about isolate scope. My advice to newbies is just don't use it ever.
Response to comment:
I understand the question better now. When you set a value via an expression, it sets it in the most immediate scope. So what people typically do with Angular is they read and mutate values instead of overwriting values. This entails containing things in some structure like an Object or Array.
See updated fiddle:
http://jsfiddle.net/kMybm/20/
("foo" would normally go in a controller hooked up via ngController.)
Another option, if you really want to do it "scopeless", is to not use ng-click and just handle click yourself.
http://jsfiddle.net/WnU6z/8/