1

I've a directive as follows:

<selectable-item-list items="model.items">
    <item-template>
          <span ng-bind="item.text"></span>
    </item-template>
</selectable-item-list>

The issue is in the <item-template>, where item would be a reference of currently iterated item when an internal ng-repeat is bound inside <selectable-item-list>.

AFAIK, it seems like transclusions can't see directive's scope, thus, that item.text can't be bound because there's no item at all.

How would you solve this scenario? Previously I was manually-transcluding <item-template> but that other approach had other downsides/issues.

Here's a runnable code snippet that works as sample of my real-world case:

var app = angular.module("app", []);

app.controller("some", function() {
  this.items = [{
    text: "hello"
  }, {
    text: "bye"
  }];
});

app.directive("test", function() {
  return {
    template: `<ol>
                  <li ng-repeat="item in items">
                      <div ng-transclude="itemTemplate"></div>
                  </li>
                </ol>`,
    transclude: {
      "itemTemplate": "itemTemplate"
    },
    scope: {
      "items": "="
    }
  }
});
<script src="https://code.angularjs.org/1.5.7/angular.js"></script>

<div ng-app="app" ng-controller="some as some">
  <test items="some.items">
    <item-template>
      <span ng-bind="item.text"></span>
    </item-template>
  </test>
</div>
Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • Can you show us a simplified version of the `selectable-item-list` and `item-template` template's. That might make it easier to wrap my head around it. – Matt Lishman Jul 08 '16 at 13:01
  • Also, you're right about "_AFAIK, it seems like transclusions can't see directive's scope_". This is so that the directive aren't tied together, defeating the purpose of transcluded directives. – Matt Lishman Jul 08 '16 at 13:02
  • @MattLishman Thanks for your comments. I'll try to put here a sample code so we can investigate further together. I'll do it in some hours....... – Matías Fidemraizer Jul 08 '16 at 15:56
  • @MattLishman Finally I did in 5 minutes. Check my updated question, I've added a runnable code snippet ;) – Matías Fidemraizer Jul 08 '16 at 16:09
  • @MattLishman In the whole snippet you'll see that both items are rendered but with no content, because obviously `item` can't be accessed from the item template.... – Matías Fidemraizer Jul 08 '16 at 16:15
  • @MattLishman I could find the definitive solution myself! See my added answer ;) – Matías Fidemraizer Jul 08 '16 at 19:22

1 Answers1

1

I had a wrong assumption! When I said that a transcluded content couldn't access the containing directive scope I was wrong because of this other Q&A: Why ng-transclude's scope is not a child of its directive's scope - if the directive has an isolated scope? which is absolutely outdated.

In fact, there's another answer as part of the same Q&A where someone has described that now this has been fixed and a transcluded content can access its direct directive scope using $parent.

So I fixed my issue just replacing the item property access with $parent.item and it worked!

I've added a working code snippet which has this fix:

var app = angular.module("app", []);

app.controller("some", function() {
  this.items = [{
    text: "hello"
  }, {
    text: "bye"
  }];
});

app.directive("test", function() {
  return {
    template: `<ol>
                  <li ng-repeat="item in items">
                      <div ng-transclude="itemTemplate"></div>
                  </li>
                </ol>`,
    transclude: {
      "itemTemplate": "itemTemplate"
    },
    scope: {
      "items": "="
    }
  }
});
<script src="https://code.angularjs.org/1.5.7/angular.js"></script>

<div ng-app="app" ng-controller="some as some">
  <test items="some.items">
    <item-template>
      <span ng-bind="$parent.item.text"></span>
    </item-template>
  </test>
</div>
Community
  • 1
  • 1
Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • Glad you found a working solution. I was trying to find the SO posts you linked before but couldn't. I would be careful though, from that post the top answer makes a good point "ng-transclude designed to allow directives to work with arbitrary content, and isolated scopes are designed to allow directives to encapsulate their data." I would be careful that you have now tied the implementation of `` to `` i.e `test` always needs to have a `$parent.item.text` available. If you change how `test` works, you could break `item-templates` too. Good answer though. – Matt Lishman Jul 08 '16 at 20:56
  • @MattLishman Yeah, I'm aware about that this couples the template with the whole directive, but, after all, it's an item template of an item designed for a specific directive. Coupling is fine here, isn't it? – Matías Fidemraizer Jul 08 '16 at 22:32
  • Agreed, I think if it's clear that they are coupled, there is no issue with it. Just added that to ensure others think about it. It's worth noting that I think all scopes have a `$parent` property, so this technique can be used elsewhere. – Matt Lishman Jul 08 '16 at 22:36
  • @MattLishman Yup, you're right. BTW thanks to this approach I can remove previous over-engineered solution that consisted in compiling the item template manually with `$compile` and this had a lot of issues.. – Matías Fidemraizer Jul 08 '16 at 22:38
  • @MattLishman IMHO I believe that this is a design flaw in Angular 1.x, because it would be nice to have something like this: "
    " so you can define which would be the scope within the whole transcluded content, and we wouldn't need `item.text` or `$parent.item.text`, but just `text`
    – Matías Fidemraizer Jul 08 '16 at 22:42
  • Yes, I agree, that would be nice. Putting it that way feels like it might be possible to do in angular 1 in a better way. I will continue to think about it. It's 11:45 PM here though, too late to be thinking about this now... ha – Matt Lishman Jul 08 '16 at 22:45
  • @MattLishman Lol, here is 0:46PM which is even worse than you to think about this ;D – Matías Fidemraizer Jul 08 '16 at 22:46