Why ng-transclude's scope is not a child of its directive's scope - if the directive has an isolated scope?
Asked Answered
K

3

31

Given a directive (container1) with transclude and an isolated scope, when the directive is linked then I have these scopes:

Scope 004           <-- scope of the body
    Scope 005       <-- scope of directive container1
    Scope 006       <-- scope of the ng-transclude

I expected:

Scope 004            <-- scope of the body
    Scope 005        <-- scope of the directive
         Scope 006   <-- scope of the ng-transclude

If the same directive has a shared scope instead of an isolated scope, I get the expected result.

This causes me a problem because, if the transcluded content contains another directive (component1) with an isolated scope, I get:

Scope 004             <-- scope of the body
    Scope 005         <-- scope of the directive
    Scope 006         <-- scope of the ng-transclude
          Scope 007   <-- scope of directive component1

I want to use the directives like this:

<container1>
   <component1 data="objectExposedInContainer1"/>
</container1>

But that does not work, inside component1, $scope.data is undefined because objectExposedInContainer1 is not on the right scope.

I have two questions:

  • Why ng-transclude's scope is not a child of its directive's scope if the directive has an isolated scope? Is this a bug?
  • If it's not a bug, how can a container directive pass data to it's content, if not by setting attributes like I tried.

Here is a sample where it does not work: http://plnkr.co/edit/NDmJiRzTF9e5gw8Buht2?p=preview. Because Plunker is built with Anguar, it's hard to debug with Batarang. I recommend downloading the code locally. Comment out line 10 of app.js to make it work using a shared scope.

Kinetics answered 22/7, 2013 at 13:0 Comment(2)
1) No, you got exactly what you asked for - the directive's scope is isolated. 2) Use a shared controller.Abadan
@Joe Gauterin, look at this new sample: plnkr.co/edit/Bv7B4OokkLi8bIctCIl3. Here, container1 contains component1 but without using ng-transclude. This time, even if both have isolated scopes, their scopes have the correct parent/child relationship. The presence of ng-transclude alters the result.Kinetics
A
29

Why ng-transclude's scope is not a child of its directive's scope if the directive has an isolated scope?

ng-transclude designed to allow directives to work with arbitrary content, and isolated scopes are designed to allow directives to encapsulate their data.

If ng-transclude didn't preserve scopes like that, any arbitrary content that you're transcluding would need to know the implementation details of your directive (i.e. it would need to know what's available on the isolated scope you created).

If it's not a bug, how can a container directive pass data to it's content, if not by setting attributes like I tried.

If the container directive and contained directives are coupled - i.e. you wrote both of them and need them to act together - then they should communicate via a shared controller.

If the container directive is supposed to inject content into the scope of the children (e.g. ng-repeat), then you shouldn't be using an isolated scope.


The angular documentation is quite clear on what the behaviour is supposed to be:

"In a typical setup the widget creates an isolate scope, but the transclusion is not a child, but a sibling of the isolate scope. This makes it possible for the widget to have private state, and the transclusion to be bound to the parent (pre-isolate) scope."

Abadan answered 22/7, 2013 at 17:38 Comment(3)
Thanks for this comprehensive answer.Kinetics
I created a work around but I'm not sure about the best practice, I'm hoping you guys might have some spare time to check it out? It's a custom transclude hack that guarantees the creation of a new child isolate scope in the link phase. This creates the parent -> child hierarchy so we can use $emit and $on in controllers. gist.github.com/meanJim/1c3339bde5cbeac6417dTurbid
I too adopted an approach similar to @Turbid and I too am not sure if it is a best practice. You can find a plunk here: plnkr.co/edit/Ph5lMl0ol8ayXOFr7ealCoagulant
M
12

you can manually transclude the child element

link: function(scope, element, attrs, ctrl, transclude) {
    transclude(scope, function(clone, scope) {
        element.find('.transclude-placeholder').append(clone);
    });
}
Monomial answered 7/8, 2014 at 8:35 Comment(1)
it would be nice with a full example, what is .transclude-placeholder?Winegar
B
5

The top answer is only correct for Angular up to v1.2.

Since Angular v1.3 the behaviour has changed, and it now behaves exactly as described in the "I expected" part of the question, making this question obsolete for Angular v1.3+.

Source: https://github.com/angular/angular.js/commit/fb0c77f0b66ed757a56af13f81b943419fdcbd7f

Bangs answered 30/4, 2015 at 14:15 Comment(2)
Hi, I have just updated the sample to 1.3.15 and it still does the same thing. plnkr.co/edit/PGsJRngCTCzstz85V80C?p=preview. I tried it with 1.4 too and I got the same result. Comments?Kinetics
In fact, today I asked a question for which I've provided an answer myself based on this answer! #38267788Haslett

© 2022 - 2024 — McMap. All rights reserved.