Angular.js: Wrapping elements in a nested transclude - angularjs

This seems like such a simple thing, but I am just not able to wrap my head around how to do it.
Here is what I want:
<my-buttons>
<my-button ng-click="doOneThing()">abc</my-button>
<my-button ng-click="doAnotherThing()">def</my-button>
</my-buttons>
That turns into something like this:
<ul class="u">
<li class="l"><button ng-click="doOneThing()">abc</button></li>
<li class="l"><button ng-click="doAnotherThing()">def</button></li>
</ul>
Notice how the ng-click is on the button, inside a wrapping li. However, the normal transclusion will place the ng-click on the li.
My best try is on this fiddle: http://jsfiddle.net/WTv7k/1/ There I have replaced the ng-click with a class, so it is easy to see when it works and not.
Any ideas of how to get this done? If it is really easy, maybe the tabs/pane example on the frontpage could be expanded to include a wrapper around the panes, while still keeping the attributes.

With replace:true the replacement process migrates all of the attributes / classes from the old element (<my-button ...>) to the new one (the root element in the template, <li ...> ). Transclude moves the content of the old element to the specified (ng-transclude) element. I'm not sure if there's a simple way to change which element in the template that will receive the migrated attributes.
To achieve what you want you could probably do some dom manipulation in a custom compile function in the my-button directive. However, I think it'd be a better idea to create a new isolate scope in the my-button directive:
<my-buttons>
<my-button click-fn="doOneThing()">abc</my-button>
<my-button click-fn="doAnotherThing()">def</my-button>
</my-buttons>
(notice I've changed ng-click to click-fn)
module.directive('myButtons', function () {
return {
restrict:'E',
transclude:true,
replace:true,
template:'<ul class="u" ng-transclude></ul>'
}
});
module.directive('myButton', function () {
return {
scope:{clickFn:'&'},
restrict:'E',
transclude:true,
replace:true,
template:'<li class="l"><button ng-click="clickFn()" ng-transclude></button></li>'
}
});
I've also made a working version of your fiddle.
To understand how the isolate scope works (scope:{clickFn:'&'}) I recommend you read the angular guide on directives.

Related

Run 'ng-click' inside a directive's isolated scope

Thanks in advance for taking the time to look into this question
I have serverside generated code that renders a directive wrapped around pre-rendered content.
<serverside-demo color="blue">
<p><strong>Content from Server, wrapped in a directive.</strong></p>
<p>I want this color to show: <span ng-style="{color: color}">{{color}}</span></p>
<button ng-click="onClickButtonInDirective()">Click Me</button>
</serverside-demo>
This means that 1.) the directive tag, 2.) the content inside the directive tag, 3.)the ng-click and 4.) The curly braces syntax are all generated by the server.
I want AngularJs to pick up the generated code, recompile the template and deal with the scope.
The problem is that I am having trouble getting it working. I understand that because the ng-click is inside the controller block, it is picked up not by the directive isolated scope, but the parent controllers. Instead I want the opposite... to pick up the onClickButtonInDirective scope function inside the serversideDemo link
I have created a jsfiddle best explaining my problem, which aims to clearly demonstrate the working "traditional" way of loading the template separately (which works) comparing it to the server-side way.
https://jsfiddle.net/stevewbrown/beLccjd2/3/
What is the best way to do this?
Thank you!
There are two major problem in your code
1- directive name and dom element not matched, - missing in dom element
app.directive('serverSideDemo', function() {
use <server-side-demo color="blue"> instead of <serverside-demo color="blue">
2- you need to compile the html code of server-side-demo dom with directive scope in link function
$compile(element.contents())(scope);
Working jsfiddle
Use templateUrl instead of template to fetch the content of directive from server:
app.directive('serverSideDemo', function() {
return {
restrict: 'AE',
scope: {
color: '='
},
templateUrl: 'link/that/returns/html/content',
link: function(scope, element, attrs) {
scope.onClickButtonInDirective = function() {
console.log('You clicked the button in the serverside demo')
scope.color = scope.color === 'blue' ? 'red' : 'blue';
}
}
};
});
Have a look at angular docs for more details

Angular: template from inner html and add existing data

Trying to add li from template and add items from $scope.menuItems (controller)
Items don't display
http://plnkr.co/edit/Jo7Vml?p=preview
app.directive("menuDemo", function(){
return {
templateUrl: 'tree-renderer.html'
}
});
Here you go : http://plnkr.co/edit/8YV6cdCIPCjEOq3DtjAz?p=preview
First : added the following to the directive :
scope:{
menuItems:"=menuDemo"
},
This enable the two-way binding on the data you pass to the menu-demo attribute when using your directives, without this you can't pass data to your directive.
Second : remove the script tag in the tree-renderer.html and sub-tree-renderer.html. Script tag is only usefull when using ng-include along with it. You were just define a template part without using it like this.
Third adding a top level ng-repeat for the 1st level in tree-renderer.html and remove the one from index.html.
<div ng-repeat="menuItem in menuItems">
As you can see menuItems match with what i have defined in the scope:{} part.
Fourth : I added the binding of the function addItem and addSubItem so you can define your own way of adding items to the tree outisde of the directive.
EDIT : Another plnkr where i move out the <div ng-repeat="menuItem in menuItems"> http://plnkr.co/edit/MZ3nsY6WTG3EiNKhXjAv?p=preview.
As you can see i put the menu-demo call in a <span> tag within the <li> tag. This is because they both use isoled scope and if i want to use data from the ng-repeat loop the easiest way is to move the other directive in a child DOM element.
Note that i also change the name of the binding element to menuItem (i remove the 's').
That is not a correct way to declare a directive try something like this:
angular.module('myModule')
.directive('menu', [function () {
return {
restrict: 'A',
scope: true,
templateUrl: 'tree-renderer.html',
link: function ($scope, $element, $attrs) {
//here some functions that do something
}
};
}]);
By the way, try improve your questions!

How to not refer to the controllerAs name in directive

Here is the minimal code to describe the issue. On the page, I have:
<div ng-controller='AController as a'>
<div a-directive></div>
</div>
In js, I have:
app.directive("aDirective", function($compile){
return {
scope: true,
link: function(scope, element, attrs) {
var template = "<h1>{{a.label}}</h1>";
element.append($compile(template)(scope));
}
}
});
app.controller("AController", function($scope){
self = this;
self.label = "some text";
});
That works, but the issue is that {{a.label}}, which made the view and controller/model tightly coupled. Is there any way to get rid of that a., and not to mention the controllerAs-name in the directive code at all? (just like what I did in the controller code)
To improve this you can pass the text to display as a parameter to the directive. Something like this is the initial idea:
<div a-directive="a.label"></div>
However, I DO recommend using an alias for the controller, so I made a Plunker where you can see all of this working together with some improvements.
Check it out here: http://plnkr.co/edit/1hBSBxwSEPXoj9TULzRQ?p=preview
I would also recommend to use template instead of link and restricting the directive to an element instead of using it as attribute, since it is modifying the DOM. But yeah, you could keep improving it till the end of the times :)

angularjs: click on a tag to invoke a function and pass the current tag to it

I need to handle a click on a tag that enables the opening of a popover.
I try to figure out the best way to do this with angularjs and naturally used hg-click.
<div ng-repeat="photo in stage.photos"
ng-click="openPopoverImageViewer($(this))"
>
$scope.openPopoverImageViewer = function (source) {
alert("openPopoverImageViewer "+source);
}
The issue is that I cannot manage to pass the $(this) to it.
Q1) How to pass the jQuery element?
Q2) In addition, ng-click sounds
to require the function being part of the controller: is it possible
to invoke a function in the partial instead?
You need to stop "thinking in jQuery" :)
Like #oori says, you can pass in photo.
Or better yet, create a custom directive. Directives is the way to go when you need new functionality in your dom, like an element that you can click to open an overlay. For example:
app.directive('popOver', function() {
return {
restrict: 'AE',
transclude: true,
templateUrl: 'popOverTemplate.html',
link: function (scope) {
scope.openOverlay = function () {
alert("Open overlay image!");
}
}
};
});
You can then use this as a custom elemen <pop-over> or as an attribute on regular HTML elements. Here is a plunker to demonstrate:
http://plnkr.co/edit/P1evI7xSMGb1f7aunh3G?p=preview
Update: Just to explain transclusion: When you say that the directive should allow transclusion (transclude:true), you say that the contents of the tag should be sent on to the directive.
So, say you write <pop-over><span>This will be passed on</span></pop-over>, then the span with "This will be passed on" is sent to the directive, and inserted wherever you put your ng-transclude element in your template. So if your pop-over template looks something like this:
<div>
<ng-transclude/>
</div>
Then your resulting DOM after the template has compiled will look like this:
<div>
<span>This will be passed on</span>
</div>
Pass it "photo"
<div ng-repeat="photo in stage.photos" ng-click="openPopoverImageViewer(photo)">
or the current $index
<div ng-repeat="photo in stage.photos" ng-click="openPopoverImageViewer($index)">

Custom directive interfering with ngClick

In this fiddle, why does the ngClick in the top link work, but the ngClick in the link to which I have added a custom directive completely fail to function?
<a class="regular" ng-click="clickTheLink()">A regular ng-click link</a>
<a class="disableable" disable="disableTheLink" ng-click="clickTheLink()">A disableable link!</a>
As far as I can tell, nothing I'm doing in the directive should be interfering at all with ngClick behavior, as all it does is manipulate CSS classes:
app.directive('disableable', function(){
return {
restrict: 'C',
scope: { disable: '&' },
link: function (scope, elem, attrs) {
scope.$watch(scope.disable, function (val) {
if (val){
elem.addClass('disabled');
elem.removeClass('enabled');
}
else {
elem.addClass('enabled');
elem.removeClass('disabled');
}
});
}
};
});
The thing is, each DOM element only has one scope. So if any directive uses isolate scope like you're using here, that becomes the one and only scope on the element. That scope is completely disconnected from any parent scopes, and in your example, clickTheLink isn't in there.
The simple answer is to not use isolate scope. It's a real nice syntax but you can do everything it does manually. For the '&' params, you can just use the parse service to parse the attribute expressions.
See updated working fiddle:
http://jsfiddle.net/SNQQV/3/
It's because you're creating an isolate scope on line 14 of the fiddle, the clickTheLink function only exists in the controller and not in the directive. While I highly suggest against doing it this way, you can quickly access the parent scope via $parent
<a class="disableable" target="_blank" disable="disableTheLink" ng-click="$parent.clickTheLink()">A disableable link!</a>
Putting this code in allows the fiddle to work correctly. Here's the fiddle of it: http://jsfiddle.net/bpN9b/10/
My suggestion would be to look into how ngClass work as well as ngDisabled. I think both of those will allow you to not use this directive at all.

Resources