I have a controller with a person and a header on the scope and I also have a person directive which also creates a person and modifies the header. The directive has an isolated scope, so it is not aware of the controller’s person. Let’s use it:

What is supposed to happen here? What should we see here? Let me think about it… We have a person directive which have a person on it called Directive Joe and also a header which says Directive's header. Then in our HTML we used the directive passing the controller’s header and then we put some HTML inside the directive. Alright, we should see the Directive's header and also the information about Directive Joe. That is obvious since the HTML inside the directive (which is called Transcluded html) is going to be transcluded into our directive. So our scopes are more or less like:

(The normal arrow is for new isolated scopes and the dashed is for new non-isolated scopes)

Try it

Wait a second, we have mixed result here… On the one hand, we have our Directive's header as expected but on the other hand, we got the controller’s John Doe person. That makes no sense at all. What’s going on?

The misconception here is to think that the transcluded html has access to the isolated scope or that the transcluded html is a new child scope of it (as I showed on the diagram before). The reality is that the transcluded html is a new child scope of the controller’s one. Yes, that is right:

(The normal arrow is for new isolated scopes and the dashed is for new non-isolated scopes)

Having this in mind, the result makes more sense. The transcluded html only sees what is on the controller’s scope. For the person it is clear, it is showing it as is. But what about the header? It is showing the directive’s one. Well, that isn’t true. Since we created a two-way databinding on the header, when we changed the header on the directive, the controller’s one also changed. That is why we saw Directive's header, because the controller’s header was also updated.

So, the transcluded html is a new child scope of the current scope on that DOM. In this case, the controller's scope. In the case that you put an ng-repeat like:

123456789

<bodyng-controller="MainCtrl"><divng-repeat="foo in foos"><personheader="header"><h2></h2><p>Hello, I am and,</p><p>I am a </p></person></div></body>

The scopes would be like:

(The normal arrow is for new isolated scopes and the dashed is for new non-isolated scopes)

The ways around

This is how the transclusion and its scope works by default. That doesn’t mean that we can’t do something to modify this behavior.

If we check the documentation we can see that the link function of a directive is like:

1

functionlink(scope,iElement,iAttrs,controller,transcludeFn){...}

Uh, that fifth parameter says something about transclusion. With that function, we have control of both the scope and the HTML of the transclusion. Let’s see it:

NOTE: Link parameters are fixed parameters so doesn’t matter the name you give to them.

the transclude function receives a function and an optional first parameter. What this function does is to clone the transcluded html and then you can do with it what you want. If you put a scope as the first parameter, that scope will be the one used on the cloned element. The callback function of transclude will receive the cloned DOM and also the scope attached to it.

In this case, we are using the directive’s parent scope (in this case the controller’s one) as the scope of the transcluded html and then we are receiving it in the callback function. What we do here is just append it on our directive’s DOM element. In the case we had a template on the directive, we could retrieve a DOM element and then use it to append the transcluded html, that is what I call complete control :)