ngTransclude orphaning bug?

2,050 views
Skip to first unread message

Ryan Noon

unread,
Dec 31, 2013, 10:53:14 PM12/31/13
to ang...@googlegroups.com
Hey gang,

I'm using 1.2.6 and currently playing with using ngTransclude inside of an ngRepeat for making a fancy collection view.  I noticed that I get the error:
Error: [ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found.
Only when my ngTransclude is on an element that's:

1. >= 4 levels into the directive template
2. inside of an ngRepeat
3. inside of another element with a directive like ngStyle (or ngClick) applied to it

If any of those three aren't true, everything seems to work fine.

For an example:


Notice how if you comment out the outer template <div>, remove the ngRepeat, or remove the ngStyle the right thing happens.

Is this a bug?  If so, should I make an issue? Might it be related to https://github.com/angular/angular.js/issues/4969 ?

Thanks!
Ryan

Daniel Tabuenca

unread,
Jan 1, 2014, 5:43:33 PM1/1/14
to ang...@googlegroups.com

I’ve spent some time with the angular code today looking at how transclusion is implemented, and it can get pretty complicated.

The ng-transclude directive is pretty simple, it just takes the transclude function injected into the current context and calls it. However, when you have a hierarchy of parents some without transclusion, and some with, it can get pretty difficult to figure out what the right transclude function and scope should be.

Strictly speaking, what you are trying to do should not work (based on the documentation). The definition of ng-transclude states:

Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.

In your situation the ‘nearest parent directive that uses transclusion would be the ng-repeat. So it is hard to tell if this is a bug, or just undefined/unsupported behavior based on your particular use. Also, even if it did find the right transclude function, it would be bound to the wrong scope, not letting you access item since your directive uses an isolate scope and the transclude function would always be bound to the non-isolate scope.

Like I said though, ng-transclude directive is actually quite simple, so it’s easy to make your own that you can pass the actual transclude function to use from within your own directive. For example:

app.directive('myTransclude', function() {
  return {
    link: function(scope, element, attr) {
      var transclude = scope.$eval(attr.myTransclude);
      transclude(scope, function(dom) {
        element.append(dom);
      });
    }
  }
});

Then in your directive you could use it like:

app.directive('myList', function() {
  return {
    restrict: 'E',
    transclude: true,
    replace: true,
    scope: {
      items: '='
    },
    template: [
      '<div>',
      '  <ul ng-style="listStyle">',
      '    <li ng-repeat="item in items">',
      '       <a href="{{item}}.htm" my-transclude="transcludeFn"></a>',
      '    </li>',
      '   </ul>',
      '</div>'
    ].join(''),
    compile: function($element, $attr) {
      return function(scope, element, attr, controllers, transclude) {
        scope.transcludeFn = transclude;
        scope.listStyle = {
          'background-color': 'red'
        }
      }
    }
  }
});

Notice the difference with the my-transclude directive is you have to pass it in an expression to a transclude function, and you need to put the transclude function in scope in the link function. The my-transclude directive will then be able to read this function from the scope and use it regardless of other directives or transclude functions in-between. Another advantage for your case is you can pass the transclude function the current scope, so your code can access the item variable from the ng-repeat.

Here is a plunker of your initial example modified to work with the custom transclude directive:

http://plnkr.co/edit/8U3xp2mnv4Gt5crKHf0Y?p=preview


Ryan Noon

unread,
Jan 2, 2014, 3:58:17 PM1/2/14
to ang...@googlegroups.com
Thanks a bunch for the reply.

I read that line in the docs and figured there had to be something fishy with the ngRepeat, but I was pleasantly surprised when it (sort of) worked.

Your solution worked for me pretty nicely.  It feels good to be explicit about which transclude we should be applying.  Maybe it makes sense for ngTransclude to have that be a default (optional) attribute value?

Thanks again!
Ryan


--
You received this message because you are subscribed to a topic in the Google Groups "AngularJS" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/angular/btQPjdtw9Gc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to angular+u...@googlegroups.com.
To post to this group, send email to ang...@googlegroups.com.
Visit this group at http://groups.google.com/group/angular.
For more options, visit https://groups.google.com/groups/opt_out.

Daniel Tabuenca

unread,
Jan 2, 2014, 4:11:38 PM1/2/14
to ang...@googlegroups.com
Yeah, that might be a good idea to suggest on the dev list. `ng-transclude` is a very simple and naive directive. I can imagine many situations where it would be hard for it to know what you mean without being explicit about it.

Stephen Friedrich

unread,
Jun 25, 2014, 7:59:17 AM6/25/14
to ang...@googlegroups.com
Thanks a lot for the detailed response and the plunker!
At first I was excited about that solution, but unfortunately my use case is yet a little more complex:
In terms of the plunker: How do you manage if there's another directive like "superList" wrapped around "myList" and you want to pass the children inside the usage of "<super-list>" down to "<my-list>"?

I thought about it a while, tinkered with the code, haven't got it working and now feel like my brain is tied in knots.
Reply all
Reply to author
Forward
0 new messages