I have an angular directive inside of another directive. Both use transclude: true.
I would expect if I had the following code (taken from the plunker) that I would see the same thing 3 times.
https://plnkr.co/edit/iIyU65WdMr4jDQyKZpt1?p=preview
JS:
angular.module('app', [])
.directive('myButton', myButton)
.directive('directiveWithDirective', directiveWithDirective)
.directive('directiveWithDiv', directiveWithDiv);
function myButton(){
return {
restrict: 'E',
transclude: true,
template: '<button ng-transclude> </button>'
};
}
function directiveWithDirective(){
return {
restrict: 'E',
transclude: true,
template: '<my-button ng-transclude> </my-button>'
};
}
function directiveWithDiv(){
return {
restrict: 'E',
transclude: true,
template: '<div ng-transclude> </div>'
};
}
HTML:
<div ng-app="app">
<my-button>
A Button
</my-button>
<br>
<directive-with-directive>
A Button
</directive-with-directive>
<br>
<directive-with-div>
<div>
<my-button>
A Button
</my-button>
</div>
</directive-with->
</div>
my-button and directive-with-div behave as I would expect. They include their content in the template.
However, directive-with-directive does not. I would expect the text "a button" to be included inside of my-button then, my-button be expanded into a button. Instead I see a blank directive:
<my-button ng-transclude=""> </my-button>.
I expect
<my-button ng-transclude=""><button>A Button</button> </my-button>
My questions are:
What am I misunderstanding about this? Is it related to the order in which directives are expanded by angular? Can I change this?
How can I achieve having a directive with transclusion within another transcluded directive.
I think you can solve your problem with this :
function directiveWithDirective(){
return {
restrict: 'E',
transclude: true,
template: '<my-button><ng-transclude /> </my-button>'
};
}
Related
I am trying to figure out how to pass a transclusion down through nested directives and bind to data in the inner-most directive. Think of it like a list type control where you bind it to a list of data and the transclusion is the template you want to use to display the data. Here's a basic example bound to just a single value (here's a plunk for it).
html
<body ng-app="myApp" ng-controller="AppCtrl as app">
<outer model="app.data"><div>{{ source.name }}</div></outer>
</body>
javascript
angular.module('myApp', [])
.controller('AppCtrl', [function() {
var ctrl = this;
ctrl.data = { name: "Han Solo" };
ctrl.welcomeMessage = 'Welcome to Angular';
}])
.directive('outer', function(){
return {
restrict: 'E',
transclude: true,
scope: {
model: '='
},
template: '<div class="outer"><inner my-data="model"><div ng-transclude></div></div></div>'
};
})
.directive('inner', function(){
return {
restrict: 'E',
transclude: true,
scope: {
source: '=myData'
},
template :'<div class="inner" my-transclude></div>'
};
})
.directive('myTransclude', function() {
return {
restrict: 'A',
transclude: 'element',
link: function(scope, element, attrs, controller, transclude) {
transclude(scope, function(clone) {
element.after(clone);
})
}
}
});
As you can see, the transcluded bit doesn't appear. Any thoughts?
In this case you don't have to use a custom transclude directive or any trick. The problem I found with your code is that transclude is being compiled to the parent scope by default. So, you can fix that by implementing the compile phase of your directive (this happens before the link phase). The implementation would look like the code below:
app.directive('inner', function () {
return {
restrict: 'E',
transclude: true,
scope: {
source: '=myData'
},
template: '<div class="inner" ng-transclude></div>',
compile: function (tElem, tAttrs, transclude) {
return function (scope, elem, attrs) { // link
transclude(scope, function (clone) {
elem.children('.inner').append(clone);
});
};
}
};
});
By doing this, you are forcing your directive to transclude for its isolated scope.
Thanks to Zach's answer, I found a different way to solve my issue. I've now put the template in a separate file and passed it's url down through the scopes and then inserting it with ng-include. Here's a Plunk of the solution.
html:
<body ng-app="myApp" ng-controller="AppCtrl as app">
<outer model="app.data" row-template-url="template.html"></outer>
</body>
template:
<div>{{ source.name }}</div>
javascript:
angular.module('myApp', [])
.controller('AppCtrl', [function() {
var ctrl = this;
ctrl.data = { name: "Han Solo" };
}])
.directive('outer', function(){
return {
restrict: 'E',
scope: {
model: '=',
rowTemplateUrl: '#'
},
template: '<div class="outer"><inner my-data="model" row-template-url="{{ rowTemplateUrl }}"></inner></div>'
};
})
.directive('inner', function(){
return {
restrict: 'E',
scope: {
source: '=myData',
rowTemplateUrl: '#'
},
template :'<div class="inner" ng-include="rowTemplateUrl"></div>'
};
});
You can pass your transclude all the way down to the third directive, but the problem I see is with the scope override. You want the {{ source.name }} to come from the inner directive, but by the time it compiles this in the first directive:
template: '<div class="outer"><inner my-data="model"><div ng-transclude></div></div></div>'
the {{ source.name }} has already been compiled using the outer's scope. The only way I can see this working the way you want is to manually do it with $compile... but maybe someone smarter than me can think of another way.
Demo Plunker
Lets start with some code
Html:
<rd-search-set type="'ActiveProfileContact'">
<form class="navbar-form navbar-static-top" role="search">
<rds-input />
</form>
</rd-search-set>
rds-input template:
<div class="input-group rd-search-wrap">
<div class="input-group-addon">
<i class="fa fa-search"></i>
</div>
<input type="text" value="" class="form-control" placeholder="{{'FIND_CONTACT' | translate | capitalize}}..." ng-modal="src.value" />
<div class="rd-search-state">
<i class="fa spin2D fa-spinner" ng-if="src.isBusy"></i>
<span class="text-muted rd-search-result" ng-if="!src.isBusy">{{src.amountString}}</span>
</div>
Javascript / AngularJs:
angular
.module("App")
.directive("rdSearchSet", rdSearchSet)
.directive("rdsInput", rdsInput);
function rdSearchSet() {
var directive = {
restrict: 'E',
scope: {
onSearch: "=onSearch",
searchForType: "=type",
relatedGuids: "=rdSearchRelatedGuids",
searchEventType: "=rdSearchEventType",
},
controller: "SearchController",
controllerAs: "src",
bindToController: true,
replace: false,
};
return directive;
}
rdsInput.$inject = ["rdBaseUrl"];
function rdsInput(rdBaseUrl) {
var directive = {
restrict: 'E',
replace: true,
templateUrl: rdBaseUrl + "Partials/Directives/Search/rdsInput.html",
require: "^rdSearchSet",
transclude: true,
scope: false,
};
return directive;
}
The problem
I'm having alot of trouble getting / setting data on the controller of the rdSearchSet directive. Last thing I tried is setting the rdsInput directive scope property to false, hoping that I can access the parent scope values using the controllerAs: "src" property of rdSearchSet.
My question in short: What is the best way to access the parent directive's controller(as) scope as transparent as possible? Like, use a Directive to load html and bind to parent directive scope properties, both ways.
EDIT:
I have moved the rdSearchSet directive html to a template that looks like this:
<form class="navbar-form navbar-static-top navbar-royal" role="search">
<rds-input />
</form>
<rds-list />
rdSearchSet.$inject = ["rdBaseUrl"];
function rdSearchSet(rdBaseUrl) {
var directive = {
restrict: 'E',
scope: {
onSearch: "=onSearch",
searchForType: "=type",
relatedGuids: "=rdSearchRelatedGuids",
searchEventType: "=rdSearchEventType",
},
templateUrl: rdBaseUrl + "Partials/Directives/Search/rdsSearchSet.html",
controller: "SearchController",
controllerAs: "src",
bindToController: true,
replace: false,
};
return directive;
}
The problem that still exists is that I am not able to use the ControllerAs prefix. The
text input field in rdsInput uses a ng-model="src.value" but the
value is not set in the rdSearchSet's Controller.
Two problems ... one is a simple typo for ng-model where you have ng-modal.
The other is isolated scope only works when you use a template, it doesn't work for existing html within the element.
If you move the <form> to a template your code will work
<rd-search-set></rd-search-set>
JS
function rdSearchSet() {
var directive = {
restrict: 'E',
templateUrl:'search.html',
scope: {
.....,
},
controller: "SearchController",
controllerAs: "src"
};
return directive;
}
DEMO
I trying to make a directive which accepting an attribute and hook it to the isolated scope, but the attribute value is not showing.
angular.module('app', [])
.controller('torrentController', [function() {
this.recommended = ['...'],
this.otherArray = ['...']
}])
.directive('torrentsTable', [function() {
return {
restrict: 'E',
templateUrl: 'templates/directives/torrentsTable.html',
scope: {
index: '='
},
controller: 'torrentController as torrentCtrl'
};
}]);
The idea is to use this directive to show different list of torrents with this syntax:
<torrents-table index="recommended"></torrents-table>
<torrents-table index="someOtherIndex"></torrents-table>
I wish this 2 almost same lines to show different "list" with results.
templates/directives/torrentsTable.html
<!-- I also tried with ng-repeat="torrent in torrentCtrl.recommended" -->
<!-- And is working as I excepted (It's shows the recommended array) -->
<div layout="row" ng-repeat="torrent in torrentCtrl[index]">
<div flex>Name: {{torrent.name}}</div>
<div flex>{{index}}</div>
</div>
{{index}} is not showing, and it's value is not showing.
While I actually make hardcoded ng-repeat arguments - it repeating but {{index}} is empty.
What I am doing wrong?
Your problem: how you pass key.
You use in directive:
scope: {
index: '='
},
so you should pass to directive expression, that evaluated to $scope property. So if you not inject scope - you pass undefined.
You can fix this two ways:
1) pass string instead something else
<torrents-table index="'recommended'"></torrents-table>
<torrents-table index="'someOtherIndex'"></torrents-table>
2) change directive definition to
scope: {
index: '#'
},
sample you can see in snippet below.
angular.module('app', [])
.controller('torrentController', [function() {
this.recommended = [1,2,3,4,5];
this.someOtherIndex = ['a','b','c','d','e'];
}])
.directive('torrentsTable', [function() {
return {
restrict: 'E',
template: '<div flex>{{index}}</div>'+
'<div layout="row" ng-repeat="torrent in torrentCtrl[index]">'+
' <div flex>Name: {{torrent}}</div>'+
'</div>',
scope: {
index: '='
},
controller: 'torrentController as torrentCtrl'
};
}])
.directive('torrentsTable2', [function() {
return {
restrict: 'E',
template: '<div flex>{{index}}</div>'+
'<div layout="row" ng-repeat="torrent in torrentCtrl[index]">'+
' <div flex>Name: {{torrent}}</div>'+
'</div>',
scope: {
index: '#'
},
controller: 'torrentController as torrentCtrl'
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
<div ng-app='app'>
<torrents-table index="'recommended'"></torrents-table>
<torrents-table index="'someOtherIndex'"></torrents-table>
<hr/>
<torrents-table2 index="recommended"></torrents-table2>
<torrents-table2 index="someOtherIndex"></torrents-table2>
</div>
I'm trying to learn angular directives/transclusion to control the creation of some custom panels in my application. I'm going wrong somewhere with transcluded content which is not appearing in the html.
I have the following html markup:
<div panel-widget>
<!-- this transcluded content appears -->
<div panel-header></div>
<div panel-body>This content doesn't</div>
</div>
In my browser I can see the content after the panel-widget directive but not the content in the panel-body directive. Here are my directives, pretty simple so far...
// -----------------------------------------------------------
// PANEL WIDGET DIRECTIVE
// -----------------------------------------------------------
angular.module('myApp.panel')
.directive('panelWidget', [ function() {
return {
template: '<div class="panel panel-default"><span ng-transclude></span</div>',
restrict: 'A',
transclude: true,
};
}]);
//-----------------------------------------------------------
//PANEL WIDGET DIRECTIVE
//-----------------------------------------------------------
angular.module('myApp.panel')
.directive('panelHeader', [ function() {
return {
template: '<div class="panel-heading"><h3 class="panel-title"><em>This appears</em></h3></div>',
restrict: 'A',
scope: {
headerObj: '='
}
};
}]);
// -----------------------------------------------------------
// PANEL WIDGET DIRECTIVE
// -----------------------------------------------------------
angular.module('myApp.panel')
.directive('panelBody', [ function() {
return {
template: '<div class="panel-body"><span ng-translude></span></div>',
restrict: 'A',
transclude: true,
scope: {
panelBodyObj: '='
}
};
}]);
Does anyone know why the nested ng-transclude isn't working? Possibly an issue with the scope?
Thanks in advance!
You have a simple typo:
template: '<div class="panel-body"><span ng-translude></span></div>',
Replace ng-translude with ng-transclude.
http://plnkr.co/edit/iDiImVrhgP7ZJMa2YCz4?p=preview
:-)
You have misspelled ng-transclude in the panelBody directive :)
I'm new with angular and trying to do some directives nesting, but having some problems.
Here is my code :
module.controller("TimelineController", ["$scope", "$compile", function ($scope, $compile) {
$scope.text = "ohoh";
$scope.elements = ["12", "13"];
console.log("Timeline Controller", arguments);
}]);
module.directive('timeline', function() {
return {
restrict: 'E',
transclude: true,
scope: true,
controller : "TimelineController",
link: function(scope, element, attrs, controller) {
console.log("linking timeline", arguments);
},
templateUrl:'templates/directives/timeline.html',
replace: true
};
});
module.directive('timelineEvent', function() {
return {
restrict: 'E',
transclude: true,
scope: true,
// controller : "TimelineController",
link: function(scope, element, attrs/*, controller*/) {
console.log("linking element", arguments);
},
templateUrl:'templates/directives/timeline_element.html',
replace: false
};
});
my templates :
timeline.html :
<div class="timeline">
<p>
timeline {{text}}
</p>
<div ng-repeat="element in elements">
- event {{element }}
<timeline-event ng-model="{{element}}"/>
</div>
</div>
timeline_element.html :
<div class="element">
timeline element o/ \o
</div>
my index.html :
[...]
<body>
<timeline></timeline>
</body>
And the unexpected result :
timeline ohoh
- event 12
- event 13
timeline element o/ \o
The expected result would be of course :
timeline ohoh
- event 12
timeline element o/ \o
- event 13
timeline element o/ \o
Why would the ng-repeat execute successfully, but the nested directive call only execute once? Is it not supposed to be able to use directives in loops?
Three remarks. I don't know if these are the cause (need to test it in a jsFiddle for that), but they are surely breaking something:
Why do you set transclude: true? You don't use ng-transclude in your template. You don't need transclude: true.
the ng-model on your timeline should be element instead of {{element}}
You are using scope: true, which means a new scope will be created. You probably will need to define your scope like (on both your directives).
Thus:
scope: {
element: '&' // provides a way to execute an expression in the context of the parent scope.
}
#Mark Rajcok please change following line to
<div ng-controlle="TimelineControllerCtrl">
to
<div ng-controller="TimelineController">