Rezen
Rezen

Reputation: 435

Angular.js directive transclude ngRepeat - error

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">

http://jsfiddle.net/jt4Y2/7/

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

Answers (1)

Daniel Tabuenca
Daniel Tabuenca

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:

http://jsfiddle.net/jt4Y2/6/

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:

http://jsfiddle.net/jt4Y2/10/

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

Related Questions