Search code examples
javascriptangularjshtmlangularjs-ng-transcludeangularjs-1.5

Force scope to named slot with ng-transclude


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>


Solution

  • 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>