-
Notifications
You must be signed in to change notification settings - Fork 27.4k
fix($compile): fix scoping for transclusion #12975
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1270,7 +1270,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
|
||
//================================ | ||
|
||
function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, | ||
function compile($compileNodes, transcludeFn, maxPriority, changeOuterScope, ignoreDirective, | ||
previousCompileContext) { | ||
if (!($compileNodes instanceof jqLite)) { | ||
// jquery always rewraps, whereas we need to preserve the original selector so that we can | ||
|
@@ -1292,6 +1292,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
return function publicLinkFn(scope, cloneConnectFn, options) { | ||
assertArg(scope, 'scope'); | ||
|
||
if (changeOuterScope) { | ||
scope = scope.$parent.$new(false); | ||
} | ||
|
||
options = options || {}; | ||
var parentBoundTranscludeFn = options.parentBoundTranscludeFn, | ||
transcludeControllers = options.transcludeControllers, | ||
|
@@ -1657,16 +1661,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
* @param previousCompileContext | ||
* @returns {Function} | ||
*/ | ||
function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { | ||
function compilationGenerator(eager, $compileNodes, transcludeFn, changeOuterScope, maxPriority, ignoreDirective, previousCompileContext) { | ||
if (eager) { | ||
return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); | ||
return compile($compileNodes, transcludeFn, maxPriority, changeOuterScope, ignoreDirective, previousCompileContext); | ||
} | ||
|
||
var compiled; | ||
|
||
return function() { | ||
if (!compiled) { | ||
compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); | ||
compiled = compile($compileNodes, transcludeFn, maxPriority, changeOuterScope, ignoreDirective, previousCompileContext); | ||
|
||
// Null out all of these references in order to make them eligible for garbage collection | ||
// since this is a potentially long lived closure | ||
|
@@ -1706,6 +1710,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
previousCompileContext = previousCompileContext || {}; | ||
|
||
var terminalPriority = -Number.MAX_VALUE, | ||
changeOuterScope, | ||
newScopeDirective = previousCompileContext.newScopeDirective, | ||
controllerDirectives = previousCompileContext.controllerDirectives, | ||
newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, | ||
|
@@ -1796,6 +1801,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
|
||
if (directiveValue = directive.transclude) { | ||
hasTranscludeDirective = true; | ||
changeOuterScope = directive.$$replacing; | ||
|
||
// Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. | ||
// This option should only be used by directives that know how to safely handle element transclusion, | ||
|
@@ -1815,8 +1821,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
compileNode = $compileNode[0]; | ||
replaceWith(jqCollection, sliceArgs($template), compileNode); | ||
|
||
childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority, | ||
replaceDirective && replaceDirective.name, { | ||
childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, changeOuterScope, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the order of [I do see that the two order errors cancel each other, but it is better if the order is respected] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did it so that I didn't have to explicitly pass |
||
terminalPriority, replaceDirective && replaceDirective.name, { | ||
// Don't pass in: | ||
// - controllerDirectives - otherwise we'll create duplicates controllers | ||
// - newIsolateScopeDirective or templateDirective - combining templates with | ||
|
@@ -1829,7 +1835,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
} else { | ||
$template = jqLite(jqLiteClone(compileNode)).contents(); | ||
$compileNode.empty(); // clear contents | ||
childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn); | ||
childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, changeOuterScope); | ||
} | ||
} | ||
|
||
|
@@ -1874,6 +1880,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
if (newIsolateScopeDirective) { | ||
markDirectivesAsIsolate(templateDirectives); | ||
} | ||
|
||
if (newScopeDirective || newIsolateScopeDirective) { | ||
markDirectivesAsReplacing(templateDirectives); | ||
} | ||
|
||
directives = directives.concat(templateDirectives).concat(unprocessedDirectives); | ||
mergeTemplateAttributes(templateAttrs, newTemplateAttrs); | ||
|
||
|
@@ -2162,6 +2173,15 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
} | ||
} | ||
|
||
function markDirectivesAsReplacing(directives) { | ||
// mark transcluding directives as replacing, so that their outer scope is changed to the directive's scope | ||
for (var j = 0, jj = directives.length; j < jj; j++) { | ||
if (directives[j].transclude) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this be Mmm... thinking about this again, it looks like the |
||
directives[j] = inherit(directives[j], {$$replacing: true}); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* looks up the directive and decorates it with exception handling and proper parameters. We | ||
* call this the boundDirective. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5629,6 +5629,34 @@ describe('$compile', function() { | |
}); | ||
|
||
|
||
//see issue https://github.com/angular/angular.js/issues/12936 | ||
it('should use the proper scope when being the root element of a replaced directive', function() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for completeness, would it be possible to have the same test with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's necessary, this bug didn't appear for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree we probably don't need a test for |
||
module(function() { | ||
directive('parent', valueFn({ | ||
scope: {}, | ||
replace: true, | ||
template: '<div trans>{{x}}</div>', | ||
link: function(scope, element, attr, ctrl) { | ||
scope.x = 'iso'; | ||
} | ||
})); | ||
directive('trans', valueFn({ | ||
transclude: 'content', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I copied it from the other tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lgalfaso : Originally the possible values for |
||
link: function(scope, element, attr, ctrl, $transclude) { | ||
$transclude(function(clone) { | ||
element.append(clone); | ||
}); | ||
} | ||
})); | ||
}); | ||
inject(function($rootScope, $compile) { | ||
element = $compile('<parent></parent>')($rootScope); | ||
$rootScope.x = 'root'; | ||
$rootScope.$apply(); | ||
expect(element.text()).toEqual('iso'); | ||
}); | ||
}); | ||
|
||
|
||
it('should not leak if two "element" transclusions are on the same element (with debug info)', function() { | ||
if (jQuery) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it looks like the order of
maxPriority
andchangeOuterScope
is wrong