Goal: Create behaviors using directives with communication between 2 sibling elements (each their own directive).
A behavior to use in example: The article content is hidden by default. When the title is clicked, I want the related article content to display.
The catch: The related article elements need to associate to each other without being nested in a single parent element or directive.
<div article="article1">this is my header</div>
<div id="article1" article-content>this is content for the header above</div>
<div article="article2">this is my header</div>
<div id="article2" article-content>this is content for the header above</div>
I know it would be easier to place the content inside the article directive, however this question is to find out how to solve a situation like this.
Can the content directive pass itself to the related article directive somehow?
This code isn't very useful as it is now, but it's a starting point. How would I accomplish this?
.directive('article', function(){
return {
restrict: "A",
controller: function($scope) {
$scope.contentElement = null;
this.setContentElement = function(element) {
$scope.contentElement = element;
}
},
link: function(scope, element) {
element.bind('click', function(){
// Show article-content directives that belong
// to this instance (article1) of the directive
}
}
}
}
.directive('articleContent', function(){
return {
require: "article",
link: function(scope, element, attrs, articleCtrl) {
// Maybe reference the article i belong to and assign element to it?
// I can't though because these are siblings.
}
}
}
None of the directive require options will allow you to require sibling directives (as far as I know). You can only:
require on the element, using require: "directiveName"
tell angular to search up the DOM tree using require: "^directiveName"
or require: "^?directiveName" if you don't necessarily need the parent controller
or require: "^\?directiveName" if you don't necessarily need the parent DOM wrapper
If you want sibling to sibling communication, you'll have to house them in some parent DOM element with a directive controller acting as an API for their communication. How this is implemented is largely dependent on the context at hand.
Here is a good example from Angular JS (O Reilly)
app.directive('accordion', function() {
return {
restrict: 'EA',
replace: true,
transclude: true,
template: '<div class="accordion" ng-transclude></div>',
controller: function() {
var expanders = [];
this.gotOpened = function(selectedExpander) {
angular.forEach(expanders, function(expander) {
if(selectedExpander != expander) {
expander.showMe = false;
}
});
};
this.addExpander = function(expander) {
expanders.push(expander);
}
}
}
});
app.directive('expander', function() {
return {
restrict: 'EA',
replace: true,
transclude: true,
require: '^?accordion',
scope: { title:'#' },
template: '<div class="expander">\n <div class="title" ng-click="toggle()">{{ title }}</div>\n <div class="body" ng-show="showMe" \n ng-animate="{ show: \'animated flipInX\' }"\n ng-transclude></div>\n</div>',
link: function(scope, element, attrs, accordionController) {
scope.showMe = false;
accordionController.addExpander(scope);
scope.toggle = function toggle() {
scope.showMe = !scope.showMe;
accordionController.gotOpened(scope);
}
}
}
})
Usage (jade templating):
accordion
expander(title="An expander") Woohoo! You can see mme
expander(title="Hidden") I was hidden!
expander(title="Stop Work") Seriously, I am going to stop working now.
Or you can create a service just for directive communication, one advantage of special service vs require is that your directives won't depend on their location in html structure.
The above solutions are great, and you should definitely consider using a parent scope to allow communication between your directives. However, if your implementation is fairly simple there's an easy method built into Angular that can communicate between two sibling scopes without using any parent: $emit, $broadcast, and $on.
Say for example you have a pretty simple app hierarchy with a navbar search box that taps into a complex service, and you need that service to broadcast the results out to various other directives on the page. One way to do that would be like this:
in the search service
$rootScope.$emit('mySearchResultsDone', {
someData: 'myData'
});
in some other directives/controllers
$rootScope.$on('mySearchResultsDone', function(event, data) {
vm.results = data;
});
There's a certain beauty to how simple that code is. However, it's important to keep in mind that emit/on/broadcast logic can get nasty very quickly if you have have a bunch of different places broadcasting and listening. A quick google search can turn up a lot of opinions about when it is and isn't an anti-pattern.
Some good insight on emit/broadcast/on in these posts:
http://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
http://nathanleclaire.com/blog/2014/04/19/5-angularjs-antipatterns-and-pitfalls/
If there is a list of articles and its content we can do it without any directive, using ng-repeat
<div ng-repeat="article in articles">
<div article="article1" ng-click='showContent=true'>{{article.header}}</div>
<div id="article1" article-content ng-show='showContent'>{{article.content}}</div>
</div>
So you need to define the article model in controller. We are making use of local scope created by ng-repeat.
Update: Based on your feedback, you need to link them together.You can try
<div article="article1" content='article1'>this is my header</div>
<div id="article1" article-content>this is content for the header above</div>
and in your directive
use
link: function(scope, element,attrs) {
element.bind('click', function(){
$('#'+attrs.content).show();
}
}
And the final method could be to use $rootScope.$broadcast and scope.$on methods to communicate between to controllers. But in this approach you need to track from where the message came and who is the intended recipient who needs to process it.
I had the exact same problem and I was able to solve it.
In order to get one directive to hide other sibling directives, I used a parent directive to act as the API. One child directive tells the parent it wasn't to be shown/hidden by passing a reference to its element, and the other child calls the parent toggle function.
http://plnkr.co/edit/ZCNEoh
app.directive("parentapi", function() {
return {
restrict: "E",
scope: {},
controller: function($scope) {
$scope.elements = [];
var on = true;
this.toggleElements = function() {
if(on) {
on = false;
_.each($scope.elements, function(el) {
$(el).hide();
});
} else {
on = true;
_.each($scope.elements, function(el) {
$(el).show();
});
}
}
this.addElement = function(el) {
$scope.elements.push(el);
}
}
}
});
app.directive("kidtoggle", function() {
return {
restrict: "A",
require: "^parentapi",
link: function(scope, element, attrs, ctrl) {
element.bind('click', function() {
ctrl.toggleElements();
});
}
}
});
app.directive("kidhide", function() {
return {
restrict: "A",
require: "^parentapi",
link: function(scope, element, attrs, ctrl) {
ctrl.addElement(element);
}
}
});
I had the same issue with a select all/ select item directive I was writing. My issue was the select all check box was in a table header row and the select item was in the table body. I got around it by implementing a pub/sub notification service so the directives could talk to each other. This way my directive did not care about how my htlm was structured. I really wanted to use the require property, but using a service worked just as well.
Related
I have a main controller in which i want to emit/ broadcast an event
main controller
.controller('gameCtrl', function(){
function moveToTileBy(moves)
{
var numberOfTiles = ctlr.board.tiles.length,
player = getCurrentPlayer(),
currentTileNumber = player.currentPositionTileNumber;
if((moves + currentTileNumber) > numberOfTiles)
{
// alert player not enough moves
return nextTurn();
}
// create a drag and drop
$scope.$emit('movePlayer', currentTileNumber);
$scope.$emit('activateTile', moves + currentTileNumber);
}
})
I also have a directive on an ng-repeatitems, each item has an isolated scope whose only connection to the main controller is the scope's model
directive
.directive('phTile', ['$rootScope', 'Drake', function ($rootScope, Drake) {
return {
restrict: 'A',
scope: {
tile: '#ngModel'
},
link: function (scope, element, attr) {
var elem = element[0];
console.log(scope.tile);
$rootScope.$on('movePlayer', function(){
console.log('root link move player ', arguments);
});
scope.$on('movePlayer', function(){ console.log('link scope move player', arguments);})
}
};
html
<div ng-controller="gameCtrl as ctlr">
<div ng-repeat="(i, tile) in ctlr.board.tiles" class="w3-col tile tile-{{tile.number}}" ng-repeat-end-watch="ctlr.resize()" ng-model="tile" ph-tile><span>{{tile.number}}</span></div>
</div>
I want the above event in the controller to trigger a series of dom manipulations to 1 or more item DOM elements
The dilemma i am facing is that I am not sure how/ where to build the logic to listen to the said event and proceed with the logic for dom manipulation....
Should it be
1) In a directive controller
return {
restrict: 'A',
scope: {
tile: '=ngModel'
}, controller: function($scope){ $scope.$on('move', function(){ /* manipulate dom element */}); }
2) Or in the link
return {
restrict: 'A',
scope: {
tile: '=ngModel'
}, link: function(scope, element, attr){ scope.$on('move', function(){ /* manipulate dom element */}); }
In addition I need to access the isolated scope's "tile" object and the directive's "element" in order to proceed with the dom manipulation.
It looks like you missed finishing submitting your question, but, in a nutshell, the manipulation of DOM elements should be in link.
Based on what you are starting to write at the bottom ('In addition I need to access the scope's "tile" object and "element" in order to proceed with the'), having the full directive and html, preferably in a demo, would help myself or somebody troubleshoot. I will update this if more information is provided.
Mark Rajcok has done an excellent job explaining the differences between links and controllers here: Difference between the 'controller', 'link' and 'compile' functions when defining a directive
Im trying to understand what is the best GENERIC approach to communicate between parent and child directive with isolated scopes (they might be reusable items).
meaning if child directive needs to update parent directive in some manner (both have isolated scopes),
should I
pass a callback function :
e.g:
.directive('filterReviewStepBox', function () {
return {
restrict: 'EA',
scop: {
//some data
},
template: '<div>text<reusable-dir></reusable-dir call-back="foo"></div>',
link: function (scope, elem, attrs) {
scope.foo = function () {
console.log('bar');
};
}
};
}).directive('reusableDir', function () {
return {
restrict: 'EA',
scope: {
callBack: '=callBack'
//other data
},
template: '<div>text<button type="button" ng-click="bar"></button></div>',
link: function (scope, elem, attrs) {
scope.bar = function () {
scope.callBack();
}
}
};
});
or should I use $emit():
e.g:
directive('filterReviewStepBox', function () {
return {
restrict: 'EA',
scope: {
// some data
},
template: '<div>text<reusable-dir></reusable-dir></div>',
link: function (scope, elem, attrs) {
scope.$on('foo', function () {
console.log('bar');
});
}
};
}).directive('reusableDir', function () {
return {
restrict: 'EA',
scope: { //some data
},
template: '<div>text<button type="button" ng-click="bar"></button></div>',
link: function (scope, elem, attrs) {
scope.bar = function () {
scope.$emit('foo');
};
}
};
});
I feel that emit would be easier to understand on a larger scale but worried about performance and overhead, but Im still unsure
tried looking for the best approach online but Im still stomped
EDIT
I forgot about the
require
option.
but I'm still not sure this is the most correct solution.
Since this doesn't allow me to reuse the child or grandchild and kind of makes the directive a single purpose item.
For this purpose the best is to utilize "require" attribute.
Complete guide to directives tell us this about require attribute : https://docs.angularjs.org/api/ng/service/$compile
Require another directive and inject its controller as the fourth
argument to the linking function. The require takes a string name (or
array of strings) of the directive(s) to pass in.
Require just tells the directive it should look for some parent directive and take its controller. You can tell directive to search in parent elements with ^ prefix and tell if this requirement is optional with ? prefix.
I have modified your example, so reusableDir can call filterReviewStepBox controller, but can be also used alone.
http://jsbin.com/gedaximeko/edit?html,js,console,output
angular.module('stackApp', [])
.directive('filterReviewStepBox', function () {
return {
restrict: 'EA',
scope: {
// some data
},
template: '<div>text<reusable-dir></reusable-dir></div>',
link: function (scope, elem, attrs) {
},
controller : function() {
this.notify = function(value) {
console.log('Notified : ' + value);
};
},
controllerAs: 'vm',
bindToController: true
};
}).directive('reusableDir', function () {
return {
restrict: 'EA',
scope: { //some data
},
require: '?^filterReviewStepBox',
template: '<div>text<button type="button" ng-click="bar()"></button></div>',
link: function (scope, elem, attrs, controller) {
scope.bar = function () {
if (controller) {
controller.notify('foo');
}
};
}
};
});
Personally, I try to avoid generating my own custom events. Mainly because it's incredibly easy to cause unwanted event propagation, or bubbling (often causing unwanted scope digests), but also because of the systematic 'noise' that in bigger applications can become very hard to manage.
My own personal views aside though, and if it's a more 'future-proof' approach you're looking for, the callback approach is by far the safest. Here's why...
Angular2 encourages developers to expose onChange attributes from directives and components that depend on child-to-parent communication. Note that these are nothing more than bound functions, and should be defined accordingly in the bindings or scopes of the given component or directive.
Similar to the above, Angular2 documents an ngOnChanges method that can be defined by any controller wishing to subscribe to changes to it's bindings. If supplied, this method is called before any DOM manipulation occurs (with the new bound values), so it's a perfect candidate for a more 'general' parent to child communication over the current AngularJS $scope.$watch implementation.
Something that wasn't mentioned however, is that communication via services is still recommended, in both AngularJS and Angular2.
ngOnChanges: https://angular.io/docs/js/latest/api/core/OnChanges-interface.html
I think it is not just about the performance, you also need to take few more factors into consideration from design and maintenance perspective.
Callback method: This is perhaps the most efficient option. The child directive would just call one method of parent directive, no
overheads. There are quite a few options explained here:
https://stackoverflow.com/a/27997722/2889528
Emit (or for that matter broadcast): This method publishes the event to all $scope(s) up (emit) or down (broadcast) to hierarchy,
relatively costly but it gives you a flexibility to watch and handle
the event wherever $scope is available.
Injectable Common Service: This option can be used when you want to call some method where $scope is not available. However, it created tight dependency on service. This is more of a
pub-sub design.
All:
Suppose I have two directives( dir1 and dir2) , which are both isolated scope. From some posts, I learnt that I need to use "require" to get scope of the other directive, but there is one question confused me so much:
Suppose I use ng-repeat generated a lot of dir1 and dir2, how do I know in certain dir1, which specific dir2's controller scope is required( cos there are many dir2 and in my understanding, all those scopes in dir2 controller are independent to each other)?
For example:
app.directive("dir1", function(){
var counter = 0;
return {
restrict:"AE",
scope:{},
template: "<button class='dir1_btn'>highlight dir2_"+(couter++)+" </button>",
link:function(scope, EL, attrs){
EL.find("button.dir1_btn").on("click", function(){
// How to let according dir2 know this click?
})
}
}
})
app.directive("dir2", function(){
var counter = 0;
return {
restrict:"AE",
scope:{},
template: "<span class='dir2_area'>This is dir2_"+(couter++)+" </span>",
link:function(scope, EL, attrs){
// Maybe put something here to listening the event passed from dir1?
}
}
})
And the html like( for simple purpose, I just put 2 of each there, actually it will generated by ng-repeat) :
<dir1></dir1>
<dir2></dir2>
<dir1></dir1>
<dir2></dir2>
Consider this just like the switch and light, dir1 is the switch to open(by change background-color) according light (dir2).
In actual project, what I want to do is angularJS directive version
sidemenu and scrollContent, each item in sidemenu is a directive,
click it will make according content(another directive) to auto scroll
to top.
I wonder how to do this? I know this is easy in jQuery, just wondering how to hook this into AngularJS data-driven pattern.
Thanks
The most important thing to note here is that I think you want to use ng-class Since you are creating both directives in an ng-repeat, I assume you are iterating over a list of objects (even if they are two separate ng-repeats, if you iterate over the same list of objects in both it will work. JQuery should not be necessary)? Attach an ngClass object to each object you iterate over, put it on an ng-class attribute in your dir2, then give dir1 access to change it. ngClass provides animation hooks if you want to animate the transition. The rest of my answer may help, though I would like to redo it now that I thought of ng-class. I have to get back to work for now though. I'll watch for feedback and try to answer quickly if you have questions.
I think there are probably a few ways to better accomplish what you are trying to do. It is not clear why both of your directives need to have isolate scopes. As I use angular more I find that though isolating a scope is a powerful technique, it is best to avoid over using it.
As for the require directive property, this post explains how to make directives communicate via their controllers very well.
I have two possible suggestions for you.
Make it one directive
Why can't you just put the templates into one?
Or if as I assume there is some reason they need to be separate, you could consider just sharing an object between them.
<div ng-repeat='obj in sharedDirObjs'>
<dir1 shared-dir-obj='obj'></dir1>
<dir2 shared-dir-obj='obj'></dir2>
</div>
app.controller('ctrl', function() {
$scope.sharedDirObjs = [obj1, obj2, obj3]
});
app.directive("dir1", function(){
var counter = 0;
return {
restrict:"AE",
scope:{sharedDirObj : '='},
template: "<button class='dir1_btn' ng-click='clickFn()'>highlight dir2_"+(couter++)+" </button>",
link:function(scope, EL, attrs){
var dir1vars...
scope.clickFn = function(){
// dir1 logic...
scope.sharedDirObj.dir2.clickFn(dir1vars...);
};
}
}})
app.directive("dir2", function(){
var counter = 0;
return {
restrict:"AE",
scope:{sharedDirObj : '='},
template: "<span class='dir2_area'>This is dir2_"+(couter++)+" </span>",
link:function(scope, EL, attrs){
scope.sharedDirObj.dir2 = {};
scope.sharedDirObj.dir2.clickFn(dir1vars...) {
// access to dir2 vars
};
}
}})
Similarly, you could create a service that holds an array of objects that are shared by injecting the service and indexed using the $index from the ng-repeat, or you could use an id system as PSL suggests. Note that the solution I describe above could work with isolate scope, or without it using scope.$eval(attr.sharedObj); on either or both of your directives. The answer to this question provides a solid runthrough of when and why to use isolated scope. In any case it would likely be best not to pipe functions through a shared object as I am showing above, and timing issues would need to be dealt with. Better would be to store properties on the object and set a scope.$watch on them in your dir2.
You may have to use some sort of strategy. Some kind of identifier hook up. Clearly you cannot use require(to require the controller of a directive and you don't have any also it can only look up to ancestors or itself not siblings). For example you could add an id attribute and a for attribute and target the element with a selection based on specific attribute value and fire an event. With this position of related element does not matter.
Your directive could look like:
<dir1 dir-for="id1"></dir1>
<dir2 dir-id="id1"></dir2>
<dir1 dir-for="id2"></dir1>
<dir2 dir-id="id2"></dir2>
and simple implementation:
.directive("dir1", function($document) {
var counter = 0;
return {
restrict: "AE",
scope: {
dirFor: '#'
},
template: "<button class='dir1_btn' ng-click='handleClick()'>highlight dir2_({{dirFor}}) </button>",
link: function(scope, EL, attrs) {
var $target = angular.element(
$document[0].querySelector('[dir-id="' + scope.dirFor + '"]'))
.contents().scope();
var clicked = false;
scope.handleClick = function() {
clicked = !clicked;
targetScope.$broadcast("SWITCH_CLICKED", clicked);
}
scope.$on('$destory',function() {
$target = null;
}
}
}
})
app.directive("dir2", function() {
var counter = 0;
return {
restrict: "AE",
scope: {
dirId: '#'
},
template: "<span class='dir2_area' ng-class=\"{true:'on', false:'off'}[status]\">This is dir2_{{dirId}}</span>",
link: function(scope, EL, attrs) {
console.log(scope.$id);
scope.status = false;
scope.$on('SWITCH_CLICKED', function(e, data) {
scope.status = data;
});
}
}
});
Demo
var app = angular.module('app', []).controller('ctrl', angular.noop);
app.directive("dir1", function($document) {
var counter = 0;
return {
restrict: "AE",
scope: {
dirFor: '#'
},
template: "<button class='dir1_btn' ng-click='handleClick()'>highlight dir2_({{dirFor}}) </button>",
link: function(scope, EL, attrs) {
var $target = angular.element($document[0].querySelector('[dir-id="' + scope.dirFor + '"]')).contents();
var clicked = false;
scope.handleClick = function() {
clicked = !clicked;
$target.scope().$broadcast("SWITCH_CLICKED", clicked);
}
scope.$on('$destroy',function() {
$target = null;
});
}
}
})
app.directive("dir2", function() {
var counter = 0;
return {
restrict: "AE",
scope: {
dirId: '#'
},
template: "<span class='dir2_area' ng-class=\"{true:'on', false:'off'}[status]\">This is dir2_{{dirId}}</span>",
link: function(scope, EL, attrs) {
scope.status = false;
scope.$on('SWITCH_CLICKED', function(e, data) {
scope.status = data;
});
}
}
})
.on{
color:green;
}
.off{
color:blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<dir1 dir-for="id1"></dir1>
<dir2 dir-id="id1"></dir2>
<dir1 dir-for="id2"></dir1>
<dir2 dir-id="id2"></dir2>
</div>
I have used $document[0].querySelector('[dir-id="' + scope.dirFor + '"]')).contents().scope() to get hold of the scope, similarly you could do .controller to get hold of the controller instance as well. Current example is doing an absolute selection(with document), you could as well make it relative.
I already know that you can set up a controller within a directive, and that other directives can call the functions on that controller. Here's what my current directive looks like:
app.directive("foobar", function() {
return {
restrict: "A",
controller: function($scope) {
$scope.trigger = function() {
// do stuff
};
},
link: function(scope, element) {
// do more stuff
}
};
});
I know that I could call it like this:
app.directive("bazqux", function() {
return {
restrict: "A",
require: "foobar",
link: function(scope, element, attrs, fooBarCtrl) {
fooBarCtrl.trigger();
}
};
});
However, I want to be able to call trigger from any directive, not just my own custom ones, like this:
<button ng-click="foobar.trigger()">Click me!</button>
If that doesn't work, is there a way to bring in a third directive to make it happen? Like this?
<button ng-click="trigger()" target-directive="foobar">Click me!</button>
Thanks!
Sounds like you need an angular service. http://docs.angularjs.org/guide/dev_guide.services
This will allow you to share functionality across directives.
Here's a similar question: Sharing data between directives
One simple way of accomplishing application-wide communication between any components would be to use global events (emitted from the $rootScope). For example:
JS:
app.directive('directiveA', function($rootScope)
{
return function(scope, element, attrs)
{
// You can attach event listeners in any place (controllers, too)
$rootScope.$on('someEvent', function()
{
alert('Directive responds to a global event');
});
};
});
HTML:
<button ng-click="$emit('someEvent')">Click me!</button>
Here you're emitting an event from the child scope but it will eventually reach the $rootScope and run the previous listener.
Here's a live example: http://plnkr.co/edit/CpKtR5R357tEP32loJuG?p=preview
When talking on irc it turned out that the communication is unnecessary:
I've got an attribute-restricted directive which performs some DOM manipulation on its parent element when it's "triggered"
A solution is to keep the logic inside the same directive and just to apply the dom changes to the parent.
http://jsfiddle.net/wt2dD/5/
scope.triggerSmthOnParent = function () {
element.parent().toggleClass('whatewer');
}
I have a directive myDirective with variable type. If I run <my-directive type="X"> I want the directive to use templateUrl: x-template.html.
If I do <my-directive type="Y"> I want the directive to use templateUrl: y-template.html.
This is my current directive.
app.directive('myDirective', function() {
var myDirective = {
templateUrl: 'X-template.html',
restrict: 'E',
scope: {
type: '='
},
};
return myDirective;
});
I read thru stackoverflow and angular documentation but have not found anything that I need.
I am now trying to do something along the lines of:
if ($scope.type === 'X') {
templateUrl: 'X-template.html',
}
else if ($scope.type === 'Y') {
templateUrl: 'Y-template.html',
}
But do not know where to do it.
Do you guys know if this is possible and how?
Angular will accept a function as the template option, so you could do something like so:
.directive('myDirective', function () {
return {
templateUrl: function (tElement, tAttrs) {
if (tAttrs) {
if (tAttrs.type === 'X') {
return 'X-template.html';
}
if (tAttrs.type === 'Y') {
return 'Y-template.html';
}
}
}
}
});
For more info, see the documentation for the $compile service.
You can work around this issue using ng-include inside compile:
app.directive('myDirective', function() {
return {
restrict: 'E',
compile: function(element, attrs) {
element.append('<div ng-include="\'' + attrs.type + '-template.html\'"></div>');
}
}
});
fiddle
If you're willing to live on the bleeding edge with a build on the 1.1.x code path (note the warning attached to every 1.1.x build notes entry so I don't dilute this answer by repeating it again here), you're in luck--this very feature was just added in the 1.1.4 release on April 3rd. You can find the release notes for 1.1.4 here and the feature's task log includes a Jasmine test that demonstrates how to use the new functionality.
If you're more conservative and are using a 1.0.x release, then you won't be able to accomplish this as easily, but it can be done. Mark Rajcok's solution looks like it would fit your requirements as-stated, but I would just add a few additional notes:
Aside from its 1.1.4 release, compile-time directives don't support modification at runtime.
As of 1.1.4, you can safely modify the attributes of compile-time directives, but only from another compile-time directive.
You may want to consider replaceWith() instead of append() since <my-directive> is not a standard-defined HTML element type.
If your X and Y templates contain additional directives, I don't think you'll be able to pass attributes on <my-template> through to the root element of your template so easily.
A directive with replace: true will transfer attributes from the source element to its replacement root, but I do not think that ngInclude will do the same from is host to the root of the included template.
I also seem to recall that ngInclude does not require that its template have exactly one root element.
You could perhaps preserve attributes on a replacement parent by using replaceWith() instead of append() and wrapping the <div ng-include=""> tag within a <div></div>. The outer <div> could hold attributes and would still be accessible after the <div ngInclude> element replaced itself with loaded content.
Be aware that ngInclude creates a new scope. This subjects you to a flashing yellow klaxons warning about the dangers of primitive scope models. For more information, see this fine page from Angular's GitHub depot.
I can propose another alternative for those on 1.0.x, but it involves a fair amount of code. It's a more heavy-weight operation, but it has the upside of not only being able of switching between templates, but full-fledged directives as well. Furthermore, its behavior is more readily dynamic.
app.directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'partials/directive/my-directive.html',
link: function(scope, element, attrs, ctrl) {
// You can do this with isolated scope as well of course.
scope.type = attrs.type;
}
}
);
my-directive.js
<div ng-switch on="{{type}}">
<div ng-switch-where="X" ng-include="X-template.html"></div>
<div ng-switch-where="Y" ng-include="Y-template.html"></div>
</div>
my-directive.html
This is my version for optionally overriding a default template
templateUrl: function (elem, attrs) {
if (attrs.customTemplate) {
return '/path/to/components/tmpl/' + attrs.customTemplate + '.html';
} else {
return '/path/to/components/tmpl/directive.html';
}
}
e.g on a directive
<div my-directive custom-template="custom"></div>
I solve this problem so:
app.directive("post", function ($templateCache, $compile) {
function getTemplate(mode) {
switch (mode) {
case "create":
return "createPost.html";
case "view":
return "viewPost.html";
case "delete":
return "deletePost.html";
}
}
var defaultMode = "view";
return {
scope: {},
restrict: "AE",
compile: function (elem, attrs, transclude) {
return function ($scope, $element, $attr) {
function updateTemplate() {
$element.html("");
$compile($templateCache.get(getTemplate($scope.mode)).trim())($scope, function (clonedElement, scope) {
clonedElement.appendTo($element);
});
}
$scope.mode = $attr.mode || defaultMode;
$scope.$watch("mode", updateTemplate);
}
}
}
});
It's probably not the best way to do this, but I have no extra scope.
Ok, this might help someone here :-)
To inject your custom attr into your link or controller function use the following.
I'm at work right now but will post a fiddle later if I get a chance :-)
.directive('yourDirective', function() {
return {
restrict: 'EA',
template: '<div></div>', // or use templateUrl with/without function
scope: {
myAttibute: '#myAttr' // adds myAttribute to the scope
},
link: function(scope) {
console.log(scope.myAttibute);
}
}
}
// HTML ""
// Console will output "foo"