How to use transclusion in the below case. The intention is to use markup in the html (partials) file, than defining it in template (within the directive).
I found a great tree directive here. (source) Original: /
Instead of defining the template in the directive, I was trying to use a transcluded content. I also updated Angular to 1.2.0.rc2. Updated: /
got below error
TypeError: Property '$transclude' of object [object Object] is not a function
code:
module.directive("tree", function($pile) {
return {
restrict: "E",
transclude: true,
scope: {family: '='},
template:
'<ul>' +
'<li ng-transclude></li>' +
'<li ng-repeat="child in family.children">' +
'<tree family="child"></tree>' +
'</li>' +
'</ul>',
pile: function(tElement, tAttr) {
var contents = tElement.contents().remove();
var piledContents;
return function(scope, iElement, iAttr) {
if(!piledContents) {
piledContents = $pile(contents);
}
piledContents(scope, function(clone, scope) {
iElement.append(clone);
});
};
}
};
});
<div ng-app="myapp">
<div ng-controller="TreeCtrl">
<tree family="family">
<p>{{ family.name }}</p>
</tree>
</div>
</div>
Edit:
With David's suggestion, made some changes. /
now, it prints, Parent. changing, family
-> treeFamily
didn't work though
How to use transclusion in the below case. The intention is to use markup in the html (partials) file, than defining it in template (within the directive).
I found a great tree directive here. (source) Original: http://jsfiddle/n8dPm/
Instead of defining the template in the directive, I was trying to use a transcluded content. I also updated Angular to 1.2.0.rc2. Updated: http://jsfiddle/aZx7B/2/
got below error
TypeError: Property '$transclude' of object [object Object] is not a function
code:
module.directive("tree", function($pile) {
return {
restrict: "E",
transclude: true,
scope: {family: '='},
template:
'<ul>' +
'<li ng-transclude></li>' +
'<li ng-repeat="child in family.children">' +
'<tree family="child"></tree>' +
'</li>' +
'</ul>',
pile: function(tElement, tAttr) {
var contents = tElement.contents().remove();
var piledContents;
return function(scope, iElement, iAttr) {
if(!piledContents) {
piledContents = $pile(contents);
}
piledContents(scope, function(clone, scope) {
iElement.append(clone);
});
};
}
};
});
<div ng-app="myapp">
<div ng-controller="TreeCtrl">
<tree family="family">
<p>{{ family.name }}</p>
</tree>
</div>
</div>
Edit:
With David's suggestion, made some changes. http://jsfiddle/aZx7B/3/
now, it prints, Parent. changing, family
-> treeFamily
didn't work though
- 1 A couple issues with this: you're referencing family.name inside the transclusion, but family is part of the directive scope and won't be available. You'd have to use treeFamily.name. Also, your nested trees won't have the transcluded content. You might get further along if you use the transclude function provided to the pile function (3rd parameter) instead of ngTransclude. – David Bennett Commented Oct 1, 2013 at 18:44
- thanks david, update with some changes. – bsr Commented Oct 1, 2013 at 18:55
- I've just been doing something similar and wanted to keep my html in a template. But the recursion wouldn't work (infinite digest - I think) unless I piled it manually inside the link function. I'd really like to know why this is the case so I can actually make coding decisions based on knowledge and not 'because thats the way it is' – Stevo Commented Oct 3, 2013 at 21:49
- Does my edited answer explain why the template want outputting properly? With my last version of the code you should be able to pass whatever you want to into the your custom directive, inducing other custom directives with templates of their own. – Erstad.Stephen Commented Oct 3, 2013 at 22:06
3 Answers
Reset to default 8 +100You need to output the name of the family in the template as well: http://jsfiddle/roadprophet/DsvX6/
module.directive("tree", function($pile) {
return {
restrict: "E",
transclude: true,
scope: {family: '='},
template:
'<ul>' +
'<li ng-transclude></li>' +
'<li ng-repeat="child in family.children">' +
'<tree family="child">{{family.name}}</tree>' +
'</li>' +
'</ul>',
pile: function(tElement, tAttr, transclude) {
var contents = tElement.contents().remove();
var piledContents;
return function(scope, iElement, iAttr) {
if(!piledContents) {
piledContents = $pile(contents, transclude);
}
piledContents(scope, function(clone, scope) {
iElement.append(clone);
});
};
}
};
});
EDIT
You could also simplify by doing this: http://jsfiddle/roadprophet/DsvX6/2/
<div ng-app="myapp">
<div ng-controller="TreeCtrl">
<tree family="treeFamily">
</tree>
</div>
</div>
module.directive("tree", function($pile) {
return {
restrict: "E",
transclude: true,
scope: {family: '='},
template:
'<ul>' +
'<li ng-transclude></li>' +
'<p>{{ family.name }}</p>' +
'<li ng-repeat="child in family.children">' +
'<tree family="child"></tree>' +
'</li>' +
'</ul>',
pile: function(tElement, tAttr, transclude) {
var contents = tElement.contents().remove();
var piledContents;
return function(scope, iElement, iAttr) {
if(!piledContents) {
piledContents = $pile(contents, transclude);
}
piledContents(scope, function(clone, scope) {
iElement.append(clone);
});
};
}
};
});
EDIT Same source of the problem though. No template being passed to the inner tree directive. http://jsfiddle/roadprophet/DsvX6/3/
<div ng-app="myapp">
<div ng-controller="TreeCtrl">
<tree family="treeFamily">
<p>{{ family.name }}</p>
</tree>
</div>
</div>
template:
'<ul>' +
'<li ng-transclude></li>' +
'<li ng-repeat="child in family.children">' +
'<tree family="child"><div ng-transclude></div></tree>' +
'</li>' +
'</ul>'
You want to pile the transcluded DOM against the parent scope; you can do this automatically with the injectable $transclude
function in a directive's controller definition:
module.directive("tree", function($pile) {
return {
restrict: "E",
transclude: true,
scope: { family: '=' },
template: '<ul>' +
'<li ng-repeat="child in family.children">' +
'<tree family="child">' +
'<p>{{ child.name }}</p>' +
'</tree>' +
'</li>' +
'</ul>',
controller: function($element, $transclude) {
$transclude(function(e) {
$element.append(e);
});
},
pile: function(tElement, tAttr, transclude) {
var contents = tElement.contents().remove();
var piledContents;
return function(scope, iElement, iAttr) {
if(!piledContents) {
piledContents = $pile(contents);
}
piledContents(scope, function(clone) {
iElement.append(clone);
});
};
}
};
});
This allows you to use the parent scope property treeFamily
in your root template (also notice the use of child
in the directive's template, above):
<div ng-app="myapp">
<div ng-controller="TreeCtrl">
<tree family="treeFamily">
<p>{{ treeFamily.name }}</p>
</tree>
</div>
</div>
You can see an example here: http://jsfiddle/BinaryMuse/UzHeW/
Very very late to the party. I needed this for a project so after dwelling into it and finding other great approaches and directions, finally came up with this:
Same code as the ng-transclude
directive, but with small addition of context
binding that the directive watch and sets on the transcluded generated scope each time it changes. Same as ng-repeat
does, but this allows:
- using the customize ng-transclude with ng-repeat without the hassle of rewriting ng-repeat and like angular/2 template outlet.
- Having the transcluded content keep access the grandparent scope while receiving direct context data from the parent. Same as template outlet again.
The augmented ng-transclude function:
return function ngTranscludePostLink(
...
) {
let context = null;
let childScope = null;
...
$scope.$watch($attrs.context, (newVal, oldVal) => {
context = newVal;
updateScope(childScope, context);
});
...
$transclude(ngTranscludeCloneAttachFn, null, slotName);
...
function ngTranscludeCloneAttachFn(clone, transcludedScope) {
...
$element.append(clone);
childScope = transcludedScope;
updateScope(childScope, context);
...
}
...
function updateScope(scope, varsHash) {
if (!scope || !varsHash) {
return;
}
angular.extend(scope, varsHash);
}
}
And it's usage:
App
<my-list items="$ctrl.movies">
<div>App data: {{ $ctrl.header }}</div>
<div>Name:{{ name }} Year: {{ year }} Rating: {{ rating
}}</div>
</my-list>
MyList
<ul>
<li ng-repeat="item in $ctrl.items track by item.id">
<div>Ng repeat item scope id: {{ $id }}</div>
<cr-transclude context="item"></cr-transclude>
</li>
</ul>
Full directive code can be found here on GitHub