I have a directive with some boilerplate html, including a menu toggle button.
Within that directive (transcluded) is one of a number of different nested directives, each with it's own menu.
The outer directive has an isolated scope and the inner directive has no scope specified.
When I click the menu toggle button on the outer directive, I want to be able to show/hide the menu contained in the inner directive.
How do I communicate the change in the toggle value from the outer directive to the inner directive?
I have tried broadcasting the change in the outer directive and listening for it in the inner directive, but that doesn't seem to work.
I have tried requiring the outer directive in the inner directive - I can see the value of the toggle there, but don't see a way of watching it for changes.
Here's the link function from the outer directive:
link: function($scope, $element, $attrs){
var menuCollapsed = true;
$scope.toggleMenu = function(){
menuCollapsed = !menuCollapsed;
console.log("ToggleMenu",menuCollapsed);
$scope.$broadcast('menuCollapsed', menuCollapsed);
};
}
and the inner directive contains this:
link: function($scope, $element, $attrs, widgetCtrl){
$scope.$on('menuCollapsed', function(event, args){
console.log('menuCollapsed', event, args);
});
The log in the outer directive displays, but not the one in the inner directive.
if you use scope.emit the event will bubble up if you use scope.broadcast the event will be broadcasted down your tree.
I persoanly prefer events over watchers how ever if your child directive is a direct child of your parents directive scope, you can use standard data binding in your child dierctive attributes.
to require a parent directive in a child directive, the parent directive has to expose its API through its controller function.
those are hints for more detailed help i would need to see some code
Related
When my controller or directive reinisilize angular.element(document).ready(function(){...}); function always runs. Why?
I have two controllers and one directive. I change value from parent controller which assign to ng-repeat. Whenever I change this variable, controller reinsilize and ready function recall too.
See this link: angualr ready function
Your code is set up such that you are destroying and recreating things when you click the "change index" button:
that.changeIndex = function(){
that.arr = [{name : g++}];
that.isValid = !that.isValid;
}
ng-repeat destroys and recreates your myCon2 controllers when you create a new arr array:
<div ng-repeat="m in con.arr track by m.name" ng-controller="myCon2 as con1">
And ng-if destroys or recreates your my-dir directive when you change isValid:
<div my-dir ng-if="con.isValid">hey directive!!!!</div>
The controller and directive both attach a handler to document ready, which will run as long as the document is ready (not just as soon as it becomes ready). This makes sense because if your code loads after the document initially becomes ready, you'd usually still want it to run.
By the way, you shouldn't need to use document ready inside Angular controllers and directives. You can put your initialization code at the top of them, or you can use ng-init.
I was wondering I am able to call a bound function within a directive when part of a div is clicked. I currently have a div with another div inside that serves as a button to expand the div. The large div has a directive that makes it expandable. I would like to click on the arrow in the corner of the div and have the arrow's ng-click call a bound function inside the expandable directive. Is this possible? If so, how can I implement it?
Here's a picture of the div:
<div expandable class="boxWithBorderStyle">
<div class="arrowStyle" ng-click="someFuncInsideExpandableDirective()"> </div>
</div>
The contents - i.e. the child elements - of the element hosting the directive have a different scope than the directive if the directive uses isolate scope (scope: {}), which I suspect is what you have.
In order to link the contents against the same scope, you'd need to manually transclude them:
.directive("expandable", function(){
return {
transclude: true,
scope: {
// whatever you have now
},
link: function(scope, element, attrs, ctrls, transcludeFn){
transcludeFn(scope, function(contentClone){
element.append(contentClone);
});
// here you can expose whatever functions you want on the isolate scope:
scope.someFuncInsideExpandableDirective = function(){
// do something
}
}
};
});
The first parameter of the transclusion function transcludeFn is the scope against which the transcluded content is linked, which, in this case, is the directive's scope.
Keep in mind, though, that it is a bit awkward from the point of view of the user of your directive, since now they have HTML that refers to some "magic" variables/function (which is of-course defined by your directive) that is not apparent to someone observing the HTML. Imagine you'd encounter some directive foo which did something similar:
<div foo="abc">
{{foobar}}
<button ng-click="doSomethingFoo()">do foo</button>
</div>
and foobar and doSomethingFoo were defined inside the scope of the foo directive - it would be more difficult to decipher without knowing the specifics of foo what was going on.
I have a directive (<my-directive>) and child directives (<my-child-directive>) as below structure:
<my-directive>
<my-child-directive caption="{{student}}" ng-repeat="student in students"></my-child-directive>
<my-child-directive caption="static content1"></my-child-directive>
</my-directive>
The first child directive repeated using ng-repeat and The last child directive is a static item.
Now I have two questions here:
Question1:
In the final output, the last directive is rendering as first <li>. is there any way to render the <li>s in the same order of child directives?
Question2:
I have used a hidden div in <my-directive>'s template to render the transclude for temporary purpose. Is there any way to avoid this unwanted div?
here is my java script code:
app=angular.module('myApp', [])
app.controller("MyController",function($scope){
$scope.students=["Alex","George","Phillip"];
});
app.directive('myDirective',function(){
return{
restrict:'E',
transclude:true,
template:"<ul><li ng-repeat='item in items'>{{item.caption}}</li></ul> <div ng-transclude ng-hide='true'></div>",
controller:function($scope){
$scope.items=[];
this.addItem=function(subScope){
$scope.items.push(subScope);
}
},
link:function(scope,element,attrs){
}
};
});
app.directive('myChildDirective',function () {
return {
restrict: 'E',
require:"^myDirective",
scope:{
caption:"#"
},
link:function(scope,element,attrs,parentCtrl){
parentCtrl.addItem(scope);
}
}
})
fiddle: http://jsfiddle.net/fyds082s/5/
Can anybody help on this?
The link function of the second directive is executed before the directives that come before him. When you check the ng-repeat state while the static directive is executed it shows that the index is 0. it hasn't finished compiling the ng-repeat yet.
My guess is that angular links sibling element from last to first, the same way it links from child elements first
Update:
My guess is wrong. so my new guess it has something to do with child scopes. For example, when adding ng-if to the second directive it is indeed executed second: http://jsfiddle.net/fyds082s/7/
<my-directive>
<my-child-directive caption="{{student}}" ng-repeat="student in students"></my-child-directive>
<my-child-directive caption="static content1" ng-if="true"></my-child-directive>
</my-directive>
What order do you think the following would produce?
<my-directive>
<my-child-directive caption="one" ng-if="true"></my-child-directive>
<my-child-directive caption="two"></my-child-directive>
</my-directive>
If you answered two, one then hat's off to you.
The reason behind this somewhat unintuitive result is a "conditional rendering" directive like ng-if and ng-repeat. They both transclude their hosting element, but render it later - when $scope.$watch or $scope.$watchCollection fire an event, respectively - at which time the link function of myChildDirective is called.
The best approach is not to rely on the order of called link functions to determine the order of rendering.
I have created two directives, one of them is used within the other and it has been created just for the matter of seperation of concerns.
<div class="docinputContainer">
<textarea></textarea>
<easy-table></easy-table>
</div>
I have omitted the scope property on both of them, therefore i would have thought that i could bind a keydown eventon my parent directive, which then could call a $scope method from the child directive:
// parent
element.bind('keydown',function(e){
$scope.save();
});
// easyTable
$scope.save = function(){
// ...
}
However, this method is unknown in my parent directives $scope.
Now i have some questions:
Why is this? Shouldn't both directives share a scope?
What would be the best solution to this?
I know i could do one of the following:
Broadcast an event
Use a shared service to trigger an event
Concatenate both directives
I would prefer to share the scope, though. Any help is appreciated
Solution
I bypassed my issue by binding the element to a scope property on the parent directive, then binding the keydown event in easyTable itself, instead of the parent one.
I'm trying to create a chart directive ( not interested in existing solutions ). I want to have definable templates for each bar using a sub-directive of bartemplate. I want to iterate the bartemplate so I thought using a template with ng-repeat and ng-transclude would do the trick ... instead angular threw an error.
Error: [ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element: <div ng-repeat="bar in bars track by $index" ng-transclude="" class="ng-scope">
http://jsfiddle.net/jt4Y2/7/
So you may ask, why the transclude? I want to be able to include any element I add to the chart (ie. a label ) and not interfere with the <bartemplate>.
You have several problems with your code. First you are complicating it by doing transclusions withing transclusions within transclusions. Second, you are combining directives that already do transclusions such as ng-repeat and trying to transclude within it, which just won't work.
I have provided a simplified version of your code that does what you want:
http://jsfiddle.net/jt4Y2/6/
Let me try to explain what it is doing. First we have the barchart directive. I took the liberty of simplifying but just making the entire body act as the template for a bar rather than having a bar-template directive.
Because we have set transclude: true the actual contents inside the barchart div are stripped out of the dom, but made available through a transclude function that you can inject into the controller via the $transclude parameter. We take this function and save it on our controller, so we can access it later on.
The barchart directive is then replaced by this template:
<div xmlns="http://www.w3.org/2000/svg">
<div ng-repeat="bar in bars track by $index" render-bar></div>
</div>
Notice that there is no ng-transclude. Instead we create our own directive render-bar to render the template (this avoids any conflicts with ng-repeat and it's own transclusion).
The renderBar directive is pretty simple:
directive('renderBar', function(){
return {
require: '^barchart',
link: function(scope, element, attrs, controller){
controller.renderBarTemplate(scope, function(dom){
element.append(dom);
});
}
}
});
First, we require that there be a parent barchart directive. In the link function, we can then get its controller and access the stored transclude function which we named renderBarTemplate.
The first parameter we pass is the scope, which makes the function link against the current scope (basically the scope provided by ng-repeat which gives us access to the iteration variables including bar).
The compiled dom is returned via the a callback (the second paramter), which we can then attach to the <div> provided by ng-repeat.
Let me know if you have any questions or need additional clarification.
UPDATE:
Here's a version that let's you keep the <bartemplate> directive:
http://jsfiddle.net/jt4Y2/10/
I added the bartemplate directive:
directive('bartemplate', function(){
return {
transclude: true,
restrict: 'E',
require: '^barchart',
link: function(scope, element, attrs, barChartController, transclude){
element.remove();
barChartController.renderBarTemplate=transclude;
}
};
})
It is now responsible for taking its transclude function and storing it in the parent's controller. (so it can later be use by the render-bar directive.
Also notice the element.remove(). This is not absolutely necessary but is simply removes the remaning <bartemplate> tag from the html.