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.
Related
With Angularjs, you can bind an event to a dom element like this:
<div ng-controller="SampleController">
<a ng-click="showTheHiddenDiv()">Show the hidden div</a>
<div ng-show="showHiddenDiv">
hidden content
</div>
</div>
My question: is it possible to attach an event handler function like this from outside the controller, like this?
<a ng-click="showTheHiddenDiv()">Show the hidden div</a>
<div ng-controller="SampleController">
<div ng-show="showHiddenDiv">
hidden content
</div>
</div>
This does not work, I'm wondering if there is a special way to access the showTheHiddenDiv() function. I know i could just wrap everything in another container and make that the controller scope, but I'm wondering if its possible to do it this way.
Thanks!
Think bindings. You want to introduce a variable on the scope that the view (ng-click and inner div can bind to). It would make sense to make this variable a Boolean so that when it changes, it will update the views.
Start by creating a showDiv variable on the scope, and updating it when the anchor link is clicked:
<div ng-controller="SampleController">
Show the hidden div
<div ng-show="showDiv">
hidden content
</div>
</div>
When the link is clicked, it will toggle 'showDiv' which is saved on the scope of SampleController. Since, the ng-show directive on the inner div is bound to 'showDiv' (the same showDiv that is on the SampleController's scope), it will automatically show and hide based on the value of 'showDiv'.
[EDIT]
I failed to answer the original question, so I'll try again.
It is certainly possible to define a scope variable that is outside SampleController and bind it to the view within an inner controller:
Show the hidden div
<div ng-controller="SampleController">
<div ng-show="showDiv">
hidden content
</div>
</div>
This works because of scope inheritance. When you have scope inheritance, the child scope inherits the scope variables from the parent scope. In the above example, you have the scope of SampleController (the child scope) which inherits from the scope of the outside controller (the parent scope). If there is no parent controller, then the parent scope is just the root scope. The important point is that child scope inherits from parent scope, and inherits all its scope variables.
In the example above, ng-show is bound to showDiv, but it hasn't actually been assigned to a scope variable in any scope (yet). So initially, 'showDiv' is undefined but it remains hidden because in angular, undefined is treated as false when evaluated. Once you click the link, then showDiv is assigned !showDiv. It is at this point that the scope variable 'showDiv' is created, and since the assignment happens outside of SampleController, the scope variable is created in the parent scope - in this case, the root scope.
Since showDiv is bound to the same scope variable in both the anchor link and the ng-show directive, any changes to the scope variable 'showDiv' (such as clicking the link), will propagate to all views that are bound to it (the div that contains the hidden content).
You can always use a service and a directive to share a value between different scopes.
app.service('SharedService', function(){
this.showHiddenDiv = false;
});
app.directive('showDiv', function(SharedService){
return {
restrict: 'AC',
link : function(scope, element, attr) {
scope.showHiddenDiv = SharedService.showHiddenDiv;
scope.toggleDiv = function(){
SharedService.showHiddenDiv = SharedService.showHiddenDiv ? false : true;
}
}
}
})
html:
<a ng-click="toggleDiv()" show-div>Show the hidden div</a>
<div ng-controller="SampleController">
<div ng-show="showHiddenDiv" show-div>
hidden content
</div>
</div>
As a side note, any time you're trying to manipulate the DOM, you should be doing so in a directive, that's the angular way!
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
I'm new to angularjs. I'm using angularjs version 1.2.13.
I've come upon a scenario where after ng-show displays my DIV, I need to manipulate the DOM in order to realign some DIV columns. I cannot realign my DIVs if they are hidden.
HTML looks like this:
<div ng-controller="MyController" ng-show="IsThisShown">
<div class="column">1</div>
<div class="column">2</div>
<div class="column">3</div>
</div>
I think the way to go would be to create a "realign" directive such as:
<div class="column" realign>1</div>
<div class="column" realign>2</div>
<div class="column" realign>3</div>
But I'm not sure how I would make the directive trigger only when the parent DIV is shown (through the ng-show directive).
Any ideas ?
Thanks appreciated!
Here's a little more code:
module.controller("MyController", function($scope, FormState){
$scope.$watch(function(){ return FormState.showGrid; }, function(newVal, oldVal){
//At this point in time, right here, the DOM has not been updated...
$scope.IsThisShown = newVal;
//At this point in time, right here the DOM has still not been updated...
//the DOM gets updated when the function exists
});
});
You have a few different options.
One option you can explore is using an isolate scope with your realign directive.
See this example here:
http://plnkr.co/edit/lO2U4GZcEm4K1qGLpsFV
You don't have to use the isolate scope as Angular scope is prototypical by nature (though with a slight gotcha with regards to primitives), but I figured I'd throw this into the example so you can see isolate scope in action. In this example I'm using an isolate scope with an execute expression.
Just a random example that builds on your description by randomly repositioning some div's after the parent is shown.
Hopefully that helps.
There are a lot of ways, but the way that immediately comes to mind is:
<div ng-controller="MyController" ng-show="IsThisShown">
<div class="column" realign="IsThisShown">1</div>
<div class="column" realign="IsThisShown">2</div>
<div class="column" realign="IsThisShown">3</div>
</div>
And have your new directive observe its argument and do the realignment when it goes true.
The other answers are good (and standard), but tie the realign directive to being shown/hidden by that variable. If, for example, there are two nested ng-show's, this would break down. In my opinion, a better solution is to create an on-show directive, with the following scope:
scope: { 'onShow' : '&' }
And within the link function, set up a $scope.$watch as follows:
$scope.$watch(
function() { return $element.hasClass('ng-hide'); },
function(newVal, oldVal) { /* execute onShow here if newVal === false */ }
);
You could also (and I don't recommend this) change ng-show to ng-if. ng-if removes and adds the elements from the DOM, instead of just hiding them. In that case, the link function fires every time the element reappears.
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.