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 :)
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
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>'
};
}
I'm trying to use the ui-tinymce directive inside of another directive:
angular.module("risevision.widget.common.font-setting", ["ui.tinymce"])
.directive("fontSetting", ["$templateCache", function ($templateCache) {
return {
restrict: "AE",
template: $templateCache.get("_angular/font-setting/font-setting.html"),
transclude: false,
link: function ($scope, element, attrs) {
$scope.tinymceOptions = {
menubar: false,
statusbar: false
};
}
};
}]);
And _angular/font-setting/font-setting.html:
<div class="row">
<div class="col-md-12">
<textarea ui-tinymce="tinymceOptions" ng-model="tinymceModel"></textarea>
</div>
</div>
The TinyMCE editor shows up, but it's ignoring the configuration options I've set in $scope.tinymceOptions. That is, the menu bar and status bar still show.
Any thoughts as to why it's not working?
Thx.
I know I'm late to the party but I'm going to answer you in case someone has the same issue and can't find the answer.
TinyMce needs to be loaded only when the tinymceOptions variable has data so you need to wrap it in an ng-if:
<div class="row">
<div class="col-md-12" ng-if="tinymceOptions">
<textarea ui-tinymce="$parent.tinymceOptions" ng-model="$parent.tinymceModel"></textarea>
</div>
</div>
You can avoid using $parent (elements inside ng-if have their own scope) using controller as inside the directive if you are using Angular >1.4:
app.directive('someDirective', function () {
return {
scope: {},
bindToController: {
someObject: '=',
...
},
controllerAs: 'ctrl',
controller: function () {
var ctrl = this;
ctrl.message = 'Object loaded.';
},
template: '<div ng-if="ctrl.someObject">{{ctrl.message}}</div>'
};
});
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 am trying to do the following:
I have a recurring UI pattern called <overlay></overlay>
This overlay is dynamically created by a directive dir1 from a templateUrl.
Let's assume now I have a second UI element called <gallery></gallery>which is also dynamically created by a directive dir2.
Is it possible to pass the gallery template into the overlay creating something like:
<overlay>
<gallery></gallery>
</overlay>
Note that the Element overlay and gallery will be replaced by the template from its respective directive.
Here is a small Plunk with the updated problem ==> http://plnkr.co/edit/Bu2cwXYVQXKmjDVXaHuK?p=preview
Yes, you can use ng-transclude. Something like:
angular.module("myModule", []).
directive("overlay", function () {
return {
restrict: "E",
transclude: true,
// or templateUrl: ...
template: '<ul class="u" ng-transclude></ul>',
replace: true
};
}).
directive("gallery", function () {
return {
require: "^overlay",
restrict: "E",
scope:{cls:'#'},
transclude: true,
// or templateUrl: ...
template: '<li class="l"><button class="{{cls}}" ng-transclude></button></li>',
replace: true
};
});
Anyways, here is good example for your needs:
Demo Fiddle
You are writing transclude: true but you are not using transclusion anywhere
So instead of your code you should write as below
template: "<div class='my-generic-overlay' ng-transclude>This should be replaced by the myGallery template</div>"