Skip to content

Commit e045b8b

Browse files
fix($compile): pass transcludeFn down to nested transclude directives
If you have two directives that both expect to receive transcluded content the outer directive works but the inner directive never receives a transclusion function. This only failed if the first transclude directive was not the first directive found in compilation. Handles the regression identified in e994259 Fixes angular#7240 Closes angular#7387
1 parent 6e2ab81 commit e045b8b

File tree

2 files changed

+123
-8
lines changed

2 files changed

+123
-8
lines changed

src/ng/compile.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
936936
return linkFnFound ? compositeLinkFn : null;
937937

938938
function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) {
939-
var nodeLinkFn, childLinkFn, node, $node, childScope, childTranscludeFn, i, ii, n;
939+
var nodeLinkFn, childLinkFn, node, $node, childScope, i, ii, n, childBoundTranscludeFn;
940940

941941
// copy nodeList so that linking doesn't break due to live list updates.
942942
var nodeListLength = nodeList.length,
@@ -958,14 +958,19 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
958958
} else {
959959
childScope = scope;
960960
}
961-
childTranscludeFn = nodeLinkFn.transclude;
962-
if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) {
963-
nodeLinkFn(childLinkFn, childScope, node, $rootElement,
964-
createBoundTranscludeFn(scope, childTranscludeFn || transcludeFn)
965-
);
961+
962+
// We need to create a new boundTranscludeFn if
963+
// - a directive on this element wants to transclude
964+
// or
965+
// - there is no boundTranscludeFn already and a transcludeFn was passed in
966+
if ( nodeLinkFn.transcludeOnThisElement || (!boundTranscludeFn && transcludeFn) ) {
967+
childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude || transcludeFn);
966968
} else {
967-
nodeLinkFn(childLinkFn, childScope, node, $rootElement, boundTranscludeFn);
969+
childBoundTranscludeFn = boundTranscludeFn;
968970
}
971+
972+
nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
973+
969974
} else if (childLinkFn) {
970975
childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
971976
}
@@ -1341,7 +1346,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
13411346
}
13421347

13431348
nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
1344-
nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn;
1349+
nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
1350+
nodeLinkFn.transclude = childTranscludeFn;
1351+
13451352
previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
13461353

13471354
// might be normal or delayed nodeLinkFn depending on if templateUrl is present

test/ng/compileSpec.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,6 +1516,114 @@ describe('$compile', function() {
15161516
));
15171517

15181518

1519+
describe('nested transcludes', function() {
1520+
1521+
beforeEach(module(function($compileProvider) {
1522+
1523+
$compileProvider.directive('noop', valueFn({}));
1524+
1525+
$compileProvider.directive('sync', valueFn({
1526+
template: '<div ng-transclude></div>',
1527+
transclude: true
1528+
}));
1529+
1530+
$compileProvider.directive('async', valueFn({
1531+
templateUrl: 'async',
1532+
transclude: true
1533+
}));
1534+
1535+
$compileProvider.directive('syncSync', valueFn({
1536+
template: '<div noop><div sync><div ng-transclude></div></div></div>',
1537+
transclude: true
1538+
}));
1539+
1540+
$compileProvider.directive('syncAsync', valueFn({
1541+
template: '<div noop><div async><div ng-transclude></div></div></div>',
1542+
transclude: true
1543+
}));
1544+
1545+
$compileProvider.directive('asyncSync', valueFn({
1546+
templateUrl: 'asyncSync',
1547+
transclude: true
1548+
}));
1549+
1550+
$compileProvider.directive('asyncAsync', valueFn({
1551+
templateUrl: 'asyncAsync',
1552+
transclude: true
1553+
}));
1554+
1555+
}));
1556+
1557+
beforeEach(inject(function($templateCache) {
1558+
$templateCache.put('async', '<div ng-transclude></div>');
1559+
$templateCache.put('asyncSync', '<div noop><div sync><div ng-transclude></div></div></div>');
1560+
$templateCache.put('asyncAsync', '<div noop><div async><div ng-transclude></div></div></div>');
1561+
}));
1562+
1563+
1564+
it('should allow nested transclude directives with sync template containing sync template', inject(function($compile, $rootScope) {
1565+
element = $compile('<div sync-sync>transcluded content</div>')($rootScope);
1566+
$rootScope.$digest();
1567+
expect(element.text()).toEqual('transcluded content');
1568+
}));
1569+
1570+
it('should allow nested transclude directives with sync template containing async template', inject(function($compile, $rootScope) {
1571+
element = $compile('<div sync-async>transcluded content</div>')($rootScope);
1572+
$rootScope.$digest();
1573+
expect(element.text()).toEqual('transcluded content');
1574+
}));
1575+
1576+
it('should allow nested transclude directives with async template containing sync template', inject(function($compile, $rootScope) {
1577+
element = $compile('<div async-sync>transcluded content</div>')($rootScope);
1578+
$rootScope.$digest();
1579+
expect(element.text()).toEqual('transcluded content');
1580+
}));
1581+
1582+
it('should allow nested transclude directives with async template containing asynch template', inject(function($compile, $rootScope) {
1583+
element = $compile('<div async-async>transcluded content</div>')($rootScope);
1584+
$rootScope.$digest();
1585+
expect(element.text()).toEqual('transcluded content');
1586+
}));
1587+
});
1588+
1589+
describe('transclude and children', function() {
1590+
beforeEach(module(function($compileProvider) {
1591+
1592+
$compileProvider.directive('myExample', valueFn({
1593+
scope: {},
1594+
link: function link(scope, element, attrs) {
1595+
var foo = element[0].querySelector('.foo');
1596+
scope.children = angular.element(foo).children().length;
1597+
},
1598+
template: '<div>' +
1599+
'<div>myExample {{children}}!</div>' +
1600+
'<div ng-if="children">has children</div>' +
1601+
'<div class="foo" ng-transclude></div>' +
1602+
'</div>',
1603+
transclude: true
1604+
1605+
}));
1606+
1607+
}));
1608+
1609+
it("should not pick up too many children when transcluding", inject(function($compile, $rootScope) {
1610+
var element = $compile('<div my-example></div>')($rootScope);
1611+
$rootScope.$digest();
1612+
$rootScope.$digest();
1613+
expect(element.text()).toEqual('myExample 0!');
1614+
dealoc(element);
1615+
1616+
element = $compile('<div my-example><p></p></div>')($rootScope);
1617+
$rootScope.$digest();
1618+
$rootScope.$digest();
1619+
expect(element.text()).toEqual('myExample 1!has children');
1620+
dealoc(element);
1621+
}));
1622+
1623+
});
1624+
1625+
1626+
15191627
it("should fail if replacing and template doesn't have a single root element", function() {
15201628
module(function($exceptionHandlerProvider) {
15211629
$exceptionHandlerProvider.mode('log');

0 commit comments

Comments
 (0)