70

From the directive Angular docs, I see the compile function has 3 parameters, one of which is transclude. The only explanation the docs provide is:

transclude - A transclude linking function: function(scope, cloneLinkingFn).

I'm trying to understand what exactly you would do in the clone linking function. I don't even know what parameters get passed into it. I found one example that has one parameter called clone that appears to be an HTML element. Are there other parameters available? Which HTML element is this exactly? I'm also looking at probably using transclude: 'element' in my directive. Do the answers to those questions change when using 'element' instead of true?

I'm understanding transclusion with the simple examples, but I can't to seem to find more complex examples, especially with transclude: 'element'. I'm hoping someone can provide a more thorough explanation about all this. Thanks.

dnc253
  • 39,967
  • 41
  • 141
  • 157

1 Answers1

56

EDIT: Completely and totally changing my answer and marking this as "Community Wiki" (meaning no points for me) as I was outright wrong when I answered this

As @Jonah pointed out below, here is a really good article on the compile option of directives and using the transclusion function

The basic idea is the compile function should return a linking function. You can use the transclusion function provided inside the linking function to take a clone of the transcluded DOM element, compile it, and insert it wherever it needs to be inserted.

Here is a better example I've pulled out of my butt on Plunker

The idea of the compile function is it gives you a chance to programmatically alter the DOM elements based on attributes passed BEFORE the linking function is created and called.

// a silly directive to repeat the items of a dictionary object.
app.directive('keyValueRepeat', function ($compile){
  return {
    transclude: true,
    scope: {
      data: '=',
      showDebug: '@'
    },
    compile: function(elem, attrs, transclude) {

      if(attrs.showDebug) {                
        elem.append('<div class="debug">DEBUG ENABLED {{showDebug}}</div>');
      }

      return function(scope, lElem, lAttrs) {
        var items = [];
        console.log(lElem);
        scope.$watch('data', function(data) {

          // remove old values from the tracking array
          // (see below)
          for(var i = items.length; i-- > 0;) {
            items[i].element.remove();
            items[i].scope.$destroy();
            items.splice(i,1);
          }

          //add new ones
          for(var key in data) {

            var val = data[key],
                childScope = scope.$new(),
                childElement = angular.element('<div></div>');

            // for each item in our repeater, we're going to create it's
            // own scope and set the key and value properties on it.
            childScope.key = key;
            childScope.value = val;

            // do the transclusion.
            transclude(childScope, function(clone, innerScope) {
              //clone is a copy of the transcluded DOM element content.
              console.log(clone);

              // Because we're still inside the compile function of the directive,
              // we can alter the contents of each output item
              // based on an attribute passed.
              if(attrs.showDebug) {                
                clone.prepend('<span class="debug">{{key}}: {{value}}</span>');
              }

              //append the transcluded element.
              childElement.append($compile(clone)(innerScope));
            });

            // add the objects made to a tracking array.
            // so we can remove them later when we need to update.
            items.push({
              element: childElement,
              scope: childScope
            });

            lElem.append(childElement);
          }
        });
      };
    }
  };
});
Jeremy
  • 1,023
  • 3
  • 18
  • 33
Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
  • 2
    Thanks for the response. First of all, isn't the first parameter into the transclude function the scope? I want to make sure I'm not missing something here. Secondly, When doing `transclude: 'element'`, what can you do with transclusion that you couldn't simply do in the compile function without any transclusion? – dnc253 Nov 01 '12 at 21:20
  • Actually, you can do this. http://plnkr.co/edit/naSbXc You can save some typing this way. – maxisam Nov 02 '12 at 01:05
  • maxisam: I do realize you can do that, I was trying to think of an example, and this was the best I could think of. I guess I could have reversed the text or something instead. – Ben Lesh Nov 02 '12 at 03:21
  • Dnc253: the simple answer is that these two methods of transclusion are there to give you the ability to control everything about you transclusion via programmatic access to the process. – Ben Lesh Nov 02 '12 at 03:24
  • As for transclude taking scope as an argument, you're thinking of the injectable $transclude provider, which is slightly different from the one passed into the compile function. – Ben Lesh Nov 02 '12 at 03:26
  • I get crazy errors in my console on your plunk. Are you seeing those too? – RandallB Nov 09 '12 at 22:28
  • 2
    I really don't think you should be passing the elem in as first parameter - it really does expect a scope. – Pete BD Jan 10 '13 at 22:08
  • what is 'clone' that is being passed as the argument? – Jatin Jan 25 '13 at 09:44
  • clone is the cloned element from your directive's template. It's essentially the element that is going to be put where the directive is. – Ben Lesh Jan 25 '13 at 14:32
  • @blesh isn't `elem` that element? I mean `elem` is also put where the directive is, right? – Jatin Jan 25 '13 at 18:49
  • Yes, and no... if you're replacing with your directive, that clone element becomes your element after transclusion. There's the elem: which is the element the directive *is* or *was applied to*, and then there is your template, which is cloned in inserted into your element, and if the directive does a replace, it actually replaces your element. I hope that makes sense. It is a bit weird, for sure. – Ben Lesh Jan 25 '13 at 19:01
  • In this plnkr: http://plnkr.co/edit/EDKzfm?p=preview, `elem` refers to the actual directive but I logged `clone[0]` in console and it refers to a span which is appended to the template. Where did this new span come from? Shouldn't the `clone` be referring to the template elements? – Jatin Jan 25 '13 at 19:39
  • Excuse, me, I'm confusing myself now... `clone` is a copy of the html content of your directive. `elem` is the directive's element. If you log the innerHTML of those elements, rather than the references, you'll get a snapshot of what they looked like when the code was executed, rather than an object reference which will only tell you what it's *currently* equal too in the developer tools. – Ben Lesh Jan 25 '13 at 19:46
  • The is coming from the processing of {{name}}. – Ben Lesh Jan 25 '13 at 19:47
  • I've updated my answer to reflect this accurately. Sorry about that. – Ben Lesh Jan 25 '13 at 19:55
  • 3
    @blesh, I'm pretty sure this answer is wrong. If you open up the console and run your plnk you'll see this error: `TypeError: Cannot read property '1' of null`. This is because you are passing an element into the first argument of the transcludeLinkingFn and it expects a scope. It's spelled out clearly in the docs: `transclude - A transclude linking function: function(scope, cloneLinkingFn).` Examples like this are not an intended use case. [This article](http://liamkaufman.com/blog/2013/05/13/understanding-angularjs-directives-part1-ng-repeat-and-compile/) shows a better one. – Jonah May 29 '13 at 10:48
  • 9
    @Jonah: You are 1000% correct. My answer was plain wrong. This was a while ago, and I've learned better. Regardless, I've completely changed the answer and set it to community wiki (no points awarded). – Ben Lesh May 30 '13 at 02:37
  • 17
    @blesh, I am very glad to see some members of SO care more about getting things right than ego. Thanks for updating! – Jonah May 30 '13 at 11:20
  • 10
    No sweat. I'm more mortified to think someone came across that and learned to do something the *wrong way*! – Ben Lesh May 31 '13 at 01:26
  • 4
    Just for others that come looking the [angular docs](http://docs.angularjs.org/api/ng.$compile) include some interesting new things. `Note: The transclude function that is passed to the compile function is deperecated, as it e.g. does not know about the right outer scope. Please use the transclude function that is passed to the link function instead.` This is due to it having it's scope pre-bound. – DeMeNteD Nov 21 '13 at 10:51
  • In your example I can see this line childElement.append($compile(clone)(innerScope)); What confuses me is that it is called in the Link function; I thought the separation between compile and link function was due to performance reasons; and to avoid executing expensive tasks in the link function. – ppoliani Mar 12 '14 at 09:13
  • @ppoliani While it's true you don't want to do "expensive tasks" in your linking function, it's worth noting that your linking function shouldn't be fired very frequently. Generally it's only fired when a view is bound to a scope. $compile actually creates a function that will call the linking functions of directives it finds in the view. In this case, in order to wire up your newly cloned elements to the scope with the rest of the view, you need to $compile the partial view and call the returned function with the scope provided. – Ben Lesh Mar 12 '14 at 14:36
  • Transclusion in compile is now deprecated. It should happen in link. – superluminary Nov 21 '14 at 10:48