TL;DR
Standard ng-transclude can’t pass parent data to transcluded content. Build a custom cr-transclude directive that extends the transclude scope with context data.
The Problem
You’re building a reusable list component. You want users to define how each item renders:
<my-list items="$ctrl.movies">
<div>{{ name }} - {{ year }}</div>
</my-list>
The transcluded template needs access to each item’s data. But ng-transclude doesn’t support this.
Here’s what happens: transcluded content gets the grandparent’s scope, not the parent that renders it. Your template can access $ctrl.movies but not the individual item being rendered.
Why This Happens
AngularJS transclusion creates a scope that inherits from where the template was defined, not where it’s rendered.
When my-list iterates and renders each item, the transcluded content has no way to receive the current item data.
Angular 2+ solves this with ngTemplateOutlet and context objects. AngularJS needs a workaround.
The Solution
Create a directive that watches a context binding and extends the transclude scope:
angular.module('app').directive('crTransclude', function() {
return {
restrict: 'EA',
transclude: true,
link: function($scope, $element, $attrs, ctrl, $transclude) {
var childScope;
var context;
$transclude(function(clone, transcludedScope) {
childScope = transcludedScope;
$element.append(clone);
updateScope(childScope, context);
});
$scope.$watch($attrs.context, function(newVal) {
context = newVal;
updateScope(childScope, context);
});
function updateScope(scope, varsHash) {
if (!scope || !varsHash) return;
angular.extend(scope, varsHash);
}
}
};
});
The key insight: we’re doing exactly what ng-repeat does internally. It creates child scopes and attaches properties like $index directly to them.
Using It
Your list component template becomes:
<ul>
<li ng-repeat="item in $ctrl.items">
<cr-transclude context="item"></cr-transclude>
</li>
</ul>
Now the consumer can write:
<my-list items="$ctrl.movies">
<div>{{ name }} ({{ year }})</div>
</my-list>
Each name and year comes from the current item. The grandparent scope ($ctrl) is still accessible for headers or other data.
Limitations
The transcluded content must be a direct child of the component. You can’t nest it deeper without additional work.
Context updates are one-way. Changes in the transcluded template won’t propagate back to the source object unless you bind to properties on it.