How do I setup an element/attribute directive that acts like:
<directive value="string"></directive>
and
<div directive="string"></div>
where the string value is the same in the scope?
restrict: 'EA',
scope: {
value: '=' || '=directive' // ???
}
I know ngInclude does something similar to this with src=""
You can use ngInclude implement code, and it will work in your case because attrs['blob'] gives you String and your value typeof is String.
But if your value was Object or Number you should use $eval or $parse for converting its value from String.
scope.value = scope.$eval(attrs.value || attrs.directive);
Or simply you can do this:
scope: {
directive: '=?',
value: '=?'
},
link: function(scope) {
scope.value = scope.value || scope.directive;
}
Related
I have a parent directive which creates an instance of a third party object during link and that variable needs to be accessible to child directives.
However, there are two limitations:
There can be multiple instances of this per-page, so a singleton at the top of the javascript file won't work.
The child directives are recursive, so they have to create their own scope.
The only way I can think of is to pass that value as an attribute to each child directive. That feels inefficient but given the above constraints, I don't see any alternative.
// Some imaginary third-party object
function Tree() {}
// Root directive which creates an instance of the object, links to the page, and loads the data needed.
app.directive('tree', function() {
return {
restrict: 'E',
replace: true,
template: '<div><nodes nodes="nodes"></nodes></div>',
scope: {
nodes: '='
},
link: function(scope, $element) {
// This value needs to be accessible to all child directives
scope.tree = new Tree();
}
};
});
// A directive to render an array of nodes
app.directive('nodes', function() {
return {
restrict: 'E',
replace: true,
template: '<ol>' +
'<li ng-repeat="node in nodes track by node.id">' +
'<node node="node"></node>' +
'</li>' +
'</ol>',
scope: {
nodes: '='
}
};
});
// A directive to render a single node, and recursively any child nodes
app.directive('node', ['$compile', function($compile) {
return {
restrict: 'E',
replace: true,
scope: {
node: '='
},
template: '<div><span ng-bind="node.text"></span></div>',
link: function(scope, $element) {
if (scope.node.children && scope.node.children.length > 0) {
console.log(scope.node.children);
var tmpl = '<nodes nodes="node.children"></nodes>';
var children = $compile(tmpl)(scope);
$element.append(children);
}
// #todo Here's a good example of where I need access to scope.tree
}
};
}]);
The only solution I can think of is adding tree: '=' to the scope objects and then passing in tree="tree" to each child.
Plunker
One solution would be to not isolate the scope, and that way the child directives would inherit from their parent scope. If the parent has scope.tree = new Tree() then the nested directives would inherit scope.tree.
However, since you mentioned that there can be multiple instances of this per page, that means you probably need an isolated scope (which is what you currently have). The only way to pass data into an isolated scope is through the directive attributes in the markup.
I think you answered your own question :)
I created a d3 barchart directive using scope in the plunker http://plnkr.co/edit/yF8H9i8tyu1o2xJCN9bV
with controller having chartData in the scope.
.controller('d3Controller', ['$scope', function($scope) {
$scope.chartData = [10,20,30,40,50];
}])
and I have a bi-directional association of chartData with directive's isolated scope
scope: {
chartData: '='
},
restrict: 'EA',
replace: false,
link: function (scope, elem, attrs) {
var data = attrs.chartData.split(',');
var d3 = $window.d3;
var chart = d3.select(elem[0]);
chart
.append("div")
.attr("class", "chart")
//returns an array of all <div>...</div> elements in div
.selectAll("div")
.data(scope.chartData)
.enter()
.append("div")
.transition().ease("elastic")
.style("width", function (d) {
return d + "%"
})
.text(function (d) {
return d + "%"
});
and the associated directive is as follows
<bar-chart chart-data="chartData"></bar-chart>
This is working fine. But, I am trying to do the same using "controllerAs"
I tried making some changes, but, it is not working.
http://plnkr.co/edit/eIRkAtfJx9rlWN5LtllC
I changed the controller's scoped chartData to this
.controller('d3Ctrl', ['$scope', function($scope) {
var self = this;
self.chartData = [10,20,30,40,50];
}])
using controllerAs and bindToDirective options for directive
scope: { },
controllerAs: 'barCtrl',
controller: function() { },
bindToDirective: {
chartData: '='
},
when trying to get chartData, it is saying barCtrl is not defined.
.selectAll("div")
.data(barCtrl.chartData)
What am I doing wrong?
You are incorrectly doing some stuff in your controller As code.
1) You need to specify 2 way binding via the scope property of the settings not bindToDirective, there is no recognized property like that in the directive settings.
2) Need to use bindToController flag to specify any scope bound 2 way bound properties to be added to the controller instance and not directly on the scope. Though it is possible that you can do bindToController:{chartData:"="} it is not documented in the official doc and hence i would not recommend doing that way since it could be removed as well in the upcoming versions.
3) You can use the 4th argument to the link function as the controller instance and refer to it inside your linking function.
So it would look like
.directive('barChart', ['$window', function($window) {
var myDirective = {
controllerAs: 'barCtrl',
controller: angular.noop,
bindToController:true, //<-- Need to specify bound values to be added to the controller instance
scope: { //Need to use scope not bindToDirective
chartData: '='
},
restrict: 'EA',
replace: false,//if it is false you don't need it
//use the 4th argument as the controller instance
link: function (scope, elem, attrs, barCtrl) {
}
Demo
I'm attempting to create a reusable parent container directive. What I want is something like the following:
<parent title="Parent title">
<child></child>
</parent>
where I can have the child elements change a value in the parent's scope.
I've attempted this binding as follows (see this fiddle):
myApp.directive('parent', function() {
return {
scope: {'title': '#'},
transclude: true,
// Manually transclude so we set parent scope to be this scope.
link: function(scope, element, attrs, ctrl, transclude) {
transclude(scope.$new(), function(content) {
element.append(content);
});
},
restrict: 'EA',
template: '<div><b>PARENT:</b> {{title}}</div>',
controller: function($scope) {
$scope.inherited = true;
$scope.$watch('inherited', function(newValue, oldValue) {
if(!newValue) {
console.log('Parent: inherited switched');
}
});
}
}
});
myApp.directive('child', function() {
return {
restrict: 'EA',
scope:{
inherited: '='
},
template: '<div><b>Child:</b> inherited attribute = {{inherited}}</div>',
controller: function($scope) {
// Why is inherited not bound to parent here?
console.log($scope.inherited);
// It obviously exists in the parent...
console.log($scope.$parent.inherited);
}
}
});
From my understanding of the API, setting an object hash scope with inherited: '=' should bind to the parent property, but as you can see in the fiddle, it doesn't.
I have two questions:
Why is inherited not bound to the parent property?
How can I achieve this message passing?
Caveats:
I would prefer not to use require: '^parent', since this creates a rather strong dependency of the child on the parent (I might want to create a number of possible parent containers and a number of child directives and mix-and-match).
I absolutely will not use link in the child directive to climb to the $parent. That's a hack and won't work in general for deeply nested directives.
I am willing to use some hackery in the parent.
Thanks for any guidance.
You can either do:
<child inherited="inherited"></child>
where you actually pass in the inherited attribute, since that is what it is looking for when you do
scope:{
inherited: '='
},
Or you could do:
myApp.directive('child', function() {
return {
restrict: 'EA',
scope: false,
so that it uses it's parent's scope.
Either way, you are likely to run into some issues binding to a primitive. You will probably save yourself a lot of headache by making inherited an object, passing the object around, and when you need to display it show the object property:
JS:
$scope.inherited = {is: true};
HTML:
{{inherited.is}}
But then keep passing around the whole object.
http://jsfiddle.net/GQX9z/1/
I've just upgraded to angularjs 1.2.1, and some code which has been working before, is no longer working.
I have the following directive:
var fooFactory = function($timeout)
{
var foo =
{
scope:
{
bar: '=',
baz: '=',
onBar: '&',
onBaz: '&',
},
controller: ['$scope', '$element', '$attrs',
function($scope, el, attrs)
{
var that = this;
that.state = null;
that.main = function()
{
that.state = "init";
};
that.getState = function()
{
return that.state;
}
that.main();
$scope.foo = that;
console.debug( "foo main called", $scope.foo );
}],
link: function(scope, el, attrs)
{
},
};
return foo;
};
MyAngularApp.directive('foo', ['$timeout', fooFactory]);
Here's how I'm including this directive in the view:
<div data-foo="true">
Foo: {{foo}} <br />
Foo state: {{foo.getState()}}
</div>
The problem is, when I run this code, I do get the console.debug statement for foo main called(), and $scope.foo is correctly set to the controller of the foo directive. But in the view itself, I see no output at all for either {{foo}} or {{foo.getState()}}, as if these have not been set on the scope at all.
Is it because of the isolate scope, that the line $scope.foo = that; is not having any effect? Please advise.
Works if you update (I would rather say pollute) the one already in the parent scope:
$scope.$parent.foo = that;
console.debug( "foo main called", $scope.$parent.foo );
Weird! It seems instead of creating a model in the directive's scope it goes up the chain looking for the model in the parent scope which I believe is not even there.
Totally weird!
I am creating a simple directive however I am stuck at the beginning. My problem is that I am unable to read the property from scope in the post function. Here is my code:
<div mod type="{{subnet.isDynamic | type }}"></div>
networkInterfaces.directive('mod', function () {
return {
scope: {
type: '#'
},
link: function (scope, iElement, iAttrs, controller) {
console.log(scope.type);
if (scope.type == "Static") {
iElement.css('background', 'blue');
}
if (scope.type == "Dynamic") {
iElement.css('background', 'green');
}
}
}
});
Your scope property is really wrong and you don't need it here.
A working plnkr here : http://plnkr.co/edit/ezbzad?p=preview
Please refer to the docs for understand the scope property inside a directive : http://docs.angularjs.org/guide/directive
But if you really want to use just do that :
app.directive("mod", function(){
return {
scope: {
value: "#value"
},
link: function(scope, element, attrs){
console.log(scope.value);
}
}
});
the scope property with {} create a new isolate scope so you can't access to your value.
Adding value: "#value" the scope is bind to the value property but note that the syntax is a javascript object syntax it's :
value : "#value" and not value = "#value" !!!
Oh and a last thing : if your directive is call mod ... in your div your attribute is mod not someDirective.