Reputation: 435
I'm trying to create a chart directive ( not interested in existing solutions ). I want to have definable templates for each bar using a sub-directive of bartemplate
. I want to iterate the bartemplate
so I thought using a template with ng-repeat
and ng-transclude
would do the trick ... instead angular threw an error.
Error: [ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element: <div ng-repeat="bar in bars track by $index" ng-transclude="" class="ng-scope">
So you may ask, why the transclude? I want to be able to include any element I add to the chart (ie. a label ) and not interfere with the <bartemplate>
.
Upvotes: 2
Views: 3341
Reputation: 13681
You have several problems with your code. First you are complicating it by doing transclusions withing transclusions within transclusions. Second, you are combining directives that already do transclusions such as ng-repeat and trying to transclude within it, which just won't work.
I have provided a simplified version of your code that does what you want:
Let me try to explain what it is doing. First we have the barchart
directive. I took the liberty of simplifying but just making the entire body act as the template for a bar rather than having a bar-template
directive.
Because we have set transclude: true
the actual contents inside the barchart
div are stripped out of the dom, but made available through a transclude
function that you can inject into the controller via the $transclude
parameter. We take this function and save it on our controller, so we can access it later on.
The barchart
directive is then replaced by this template:
<div xmlns="http://www.w3.org/2000/svg">
<div ng-repeat="bar in bars track by $index" render-bar></div>
</div>
Notice that there is no ng-transclude
. Instead we create our own directive render-bar
to render the template (this avoids any conflicts with ng-repeat and it's own transclusion).
The renderBar
directive is pretty simple:
directive('renderBar', function(){
return {
require: '^barchart',
link: function(scope, element, attrs, controller){
controller.renderBarTemplate(scope, function(dom){
element.append(dom);
});
}
}
});
First, we require that there be a parent barchart
directive. In the link
function, we can then get its controller and access the stored transclude function which we named renderBarTemplate
.
The first parameter we pass is the scope, which makes the function link against the current scope (basically the scope provided by ng-repeat
which gives us access to the iteration variables including bar
).
The compiled dom is returned via the a callback (the second paramter), which we can then attach to the <div>
provided by ng-repeat
.
Let me know if you have any questions or need additional clarification.
UPDATE:
Here's a version that let's you keep the <bartemplate>
directive:
I added the bartemplate directive:
directive('bartemplate', function(){
return {
transclude: true,
restrict: 'E',
require: '^barchart',
link: function(scope, element, attrs, barChartController, transclude){
element.remove();
barChartController.renderBarTemplate=transclude;
}
};
})
It is now responsible for taking its transclude
function and storing it in the parent's controller. (so it can later be use by the render-bar
directive.
Also notice the element.remove()
. This is not absolutely necessary but is simply removes the remaning <bartemplate>
tag from the html.
Upvotes: 6