Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Nested transclusion not working with ng-repeat #7842

Closed
gustavohenke opened this issue Jun 15, 2014 · 8 comments
Closed

Nested transclusion not working with ng-repeat #7842

gustavohenke opened this issue Jun 15, 2014 · 8 comments

Comments

@gustavohenke
Copy link

Starting at 1382d4e, I'm no longer able to use a nested transclusion with ng-repeat.

My use case is the following:

<parent>
  <child items="value in array">
    {{ value }}
  </child>
</parent>

Both parent and child directives have transclude: true. parent has scope: true, child has scope: false. items attribute is replaced with ng-repeat after some parsing in the compile function.

Something I noted while debugging in Batarang is that previously, I had a scope tree like this:

- 00A
|- 00B
- 00C
|- 00D

where 00A/00C had the value from the original array and 00B/00D were the content of each item in `ng-repeat.

Now, my scope tree looks like this:

- 00A
- 00B
- 00C
- 00D

same rule applies.

Working plunker (with v1.2.17): http://plnkr.co/edit/tdbRdT8gnLKhdcGAWlNV
Reproducible plunker for v1.2.18: http://plnkr.co/edit/qA3tOaR5RyehjI02xGRW?p=preview

If I wasn't clear enough and any further info is needed, please let me know.
Thanks in advance.

@Narretz
Copy link
Contributor

Narretz commented Jun 15, 2014

Please post a plunker with a minimum reproducible example, that would help the compiler gurus a lot. Thanks!

@gustavohenke
Copy link
Author

I have edited the issue with 2 plunkers, one for v1.2.17 and another one for v1.2.18!

gustavohenke pushed a commit to injoin/frontkit that referenced this issue Jun 15, 2014
Due to angular/angular.js#7842, we can't, for now, use Angular v1.2.18, as it is bugging the dropdown directive.
@Narretz
Copy link
Contributor

Narretz commented Jun 16, 2014

Just to clarify, I am not one of the compiler gurus. ;)

but the problem also exists in the the latest beta:

http://plnkr.co/edit/DW7NgK5LON3yYoM1O2No?p=preview

ping @caitp @petebacondarwin

@petebacondarwin
Copy link
Member

Explanation:

This code is working correctly. This is similar to #7874. What you are doing is actually a bit naughty :-) and not what transclusion is designed to do.

Transcluded content by definition is supposed to be bound to the place in the DOM where it is originally found. In this case we have two transclusions:

<body>
  <parent>
    <child repeat="item in items">{{ item }} {{$id}}<br></child>
  </parent>
</body>

The first transclusion is contents of the <parent> element, which is bound to the scope where <parent> is instantiated, i.e. the scope at <body>:

    <child repeat="item in items">{{ item }} {{$id}}<br></child>

The second transclusion is the contents of the <child> element, which is bound to the scope where <child> is instantiated, which in this case is the scope of the previous transcluded elements, i.e. the scope at <body> again:

{{ item }} {{$id}}<br>

So in the transcluded elements we do not have access to the scope of the template, and so don't have access to the ng-repeat item value.

This worked previously because we had a bug in the compiler that actually bound nested transclusions incorrectly.

Solution:

The way to do this is not to use transclusion at all but to use the compile function of your directive to fix up your HTML correctly:

http://plnkr.co/edit/lf4ZRsiiDrg2xfyWvJxI?p=preview

app.factory('containerHelper', function() {
  return function(element, template) {
    template = angular.element(template);
    element = angular.element(element);

    // Extract the children from this instance of the directive
    var children = element.contents();

    // Wrap the children in our template
    var injectElement = template.find('inject');
    injectElement.replaceWith(children);

    // Append this new template to our compile element
    element.append(template);
  };
});

app.directive( "parent", function(containerHelper) {
  return {
    restrict: "E",
    scope: true,
    compile: function(element) {
      containerHelper(element, '<div><inject></inject></div>');
    }
  };
});

app.directive( "child", function(containerHelper) {
  return {
    restrict: "E",
    compile: function(element, attr) {
      template = angular.element('<div><span><inject></inject></span></div>');
      template.find("span").attr("ng-repeat", attr.repeat);
      containerHelper(element, template);
    }
  };
});

@gustavohenke
Copy link
Author

@petebacondarwin any tips for dealing with templates?
I could manually $http.get() them, but that would not be optimal; your example uses inline HTML only..

@gustavohenke
Copy link
Author

OK, I managed to make it work now.
If anyone looking into this, check this plunk, which is a fork of my previous (non working example):
http://plnkr.co/edit/TB1kpooolB0ZZlaVrc8I?p=preview

@petebacondarwin
Copy link
Member

If you want to use templates then you do indeed need $http but you should use it in partnership with $templateCache. This is how the compiler and things like ngInclude work.

gustavohenke pushed a commit to injoin/frontkit that referenced this issue Jun 23, 2014
As of Angular.js v1.2.18, nested transclusion with ng-repeat wasn't
binding to the correct scope (see angular/angular.js#7842). To fix that,
we should manually transclude the content of options and then, finally,
compile the options with ng-repeat.

Fixes #14
@gustavohenke
Copy link
Author

I was unsure because of doing $http in the compile phase of a directive.
But, already fixed it another way, as shown in the example plunk in my previous comment.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants