5

I might get some concept terribly wrong, but I don't get this to work I expected: http://plnkr.co/edit/Qe2IzMMMR5BJZJpwkx9e?p=preview

What I'm trying to do is to define a directive that gets attached to a top-level <nav> element, and then modifies the contained DOM elements in its link function (such as adding css classes to <li> etc.). However, the link function seems to only get the original directive template (<nav><ul><ng-transclude/></ul></nav), and not the transcluded/expanded DOM elements.

If this is "by design", how should I do this? It find it pretty useless to define a transcluding "root" directive, if it does not have access to the transcluded DOM tree....

Florian
  • 271
  • 3
  • 14

3 Answers3

6

Please read some of my answers about transclusion in angular:

As to your question:

  • First, it's not useless , even if not fit to your use case.
  • Yes, it's by design - but it's just the default behavior of ng-transclude.
  • If it was the opposite then everyone would be yelling about scope leaking.
  • You can do anything you want with $transclude, just be careful.
  • There are probably better solutions like creating isolated scope with bindings.

This is what you wanted (plunker):

angular.module('app').directive ('myNav', ['$timeout', function($timeout) {
  return {
    replace: false,
    transclude: true,
    template: '<nav><ul></ul></nav>',
    link: function (scope, element, attrs,ctrl,$translcude){
      $transclude(scope,function(clone){
        element.find('ul').html(clone)
      });
      var items = element.find('li'); //element.find('ng-transclude') === 1 !
      window.console.log (items.length);
    }
  };
Community
  • 1
  • 1
Ilan Frumer
  • 32,059
  • 8
  • 70
  • 84
  • I typically use nested directives if I need to do something like add classes/functionality in the transcluded elements. I've found that smaller directives work better than a single one that tries to manipulate the entire DOM. – fooby Jan 21 '14 at 18:30
  • Thanks Ilan, and no offense intended - I'm pretty sure there are good reasons for ng-transclude, but I just don't really get it, even after reading all those articles around the web ;-) Consider my example - I finally got it working without transclude at all (see my answer below). So what kind of concrete use cases do I need `ng-transclude` for? Is it just a shorthand to cloning content and inserting it into my directive template? – Florian Jan 21 '14 at 23:09
  • I mostly use `$transclude` when needed , `ng-transclude` is just a simple not flexbile shorthand directive. take a look at the [source code](https://github.com/angular/angular.js/blob/master/src/ng/directive/ngTransclude.js) and you'll get it. One thing to mention it's not just cloning and inserting it's also *compiling* it first! – Ilan Frumer Jan 21 '14 at 23:18
  • If you really want to see good examples and use cases to why, when and how to use transclusion . Take a look at `ngView` / `ngInclude` / `ngRepeat`/ `ngIf` / `ngSwitch` source code. That's what I did. – Ilan Frumer Jan 21 '14 at 23:25
  • Thanks for clarification, the `$transclude` was something I didn't know about. – Florian Jan 21 '14 at 23:26
1

(Correct answers see above from Ilan and others)

I finally got my (simple) use case working without transclude at all with the old dirty $timeout hack: http://plnkr.co/edit/FEEDYJLK9qRt0F4DNzRr?p=preview

link: function(scope, element) {
    // add to end of event queue
    $timeout(function() {
      var items = element.children('ul:first').children('li');
      window.console.log(items.length);
    }, 0);
  }

I know this is a bad thing to do, and not totally sure if this will work always, but at least seems to work for my simple case...

Florian
  • 271
  • 3
  • 14
0

I think the issue is that you have a ng-repeat within the directive so the "element" is not able to access the child nodes until the ng-repeats have been resolved. A way around this is to have your directive on each of the list tags. I'd add transclude to the tag, and then you can remove the template from your directive all together.

You'd end up with something like:

    <li ng-repeat="item in menuItems" my-nav ng-transclude>

Your directive would look like

angular.module('app').directive ('myNav', ['$timeout', function($timeout) {
  return {
  replace: false,
  transclude: true,
  compile: function (element, attrs, transclude){

     // this will always return 0 unless you split this into two directives
     // and emit or watch for the ng-repeats to complete in the parent
     // directive
     //var items = $(element).find('li'); //element.find('ng-transclude') === 1 !

     //instead showing you how to access css for the given element
     element.css( "color", "red" );
     }
  };
}]);

As I mentioned in the comments above, you could split the directive into two directives: one at the nav level and one on your ng-repeat that simply emits when the repeats are done and you can apply css accordingly as the find will then be able to find the child nodes as they are resolved. I think that approach is redundant however, as you'd be setting css for nodes to which you've already applied your change. I think as noted in one of the comments below, smaller directives work better and your project is less likely to become a transcluded mess of spaghetti like scopes. Happy coding :)

  • thanks, I understand that separation into multiple smaller directives would be the better way to go. I was migrating some JQuery code into angular that iterated/modified child-elements, so I started with this transclusion-approach. My point is that I don't really get a grasp on the usefullness of ng-transclude yet, even after reading some tutorials / articles... see my comment to Ilan below. – Florian Jan 21 '14 at 22:52