diff --git a/src/ng/compile.js b/src/ng/compile.js
index 00f174d10954..e4b42e75a9c7 100644
--- a/src/ng/compile.js
+++ b/src/ng/compile.js
@@ -622,10 +622,17 @@
*
*
* @param {string|DOMElement} element Element or HTML string to compile into a template function.
- * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives.
+ * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
+ *
+ *
+ * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
+ * e.g. will not use the right outer scope. Please pass the transclude function as a
+ * `parentBoundTranscludeFn` to the link function instead.
+ *
+ *
* @param {number} maxPriority only apply directives lower than given priority (Only effects the
* root element(s), not their children)
- * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template
+ * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
* (a DOM element/tree) to a scope. Where:
*
* * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
@@ -637,6 +644,19 @@
* * `clonedElement` - is a clone of the original `element` passed into the compiler.
* * `scope` - is the current scope with which the linking function is working with.
*
+ * * `options` - An optional object hash with linking options. If `options` is provided, then the following
+ * keys may be used to control linking behavior:
+ *
+ * * `parentBoundTranscludeFn` - the transclude function made available to
+ * directives; if given, it will be passed through to the link functions of
+ * directives found in `element` during compilation.
+ * * `transcludeControllers` - an object hash with keys that map controller names
+ * to controller instances; if given, it will make the controllers
+ * available to directives.
+ * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
+ * the cloned elements; only needed for transcludes that are allowed to contain non html
+ * elements (e.g. SVG elements). See also the directive.controller property.
+ *
* Calling the linking function returns the element of the template. It is either the original
* element passed in, or the clone of the element if the `cloneAttachFn` is provided.
*
@@ -1155,8 +1175,22 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
maxPriority, ignoreDirective, previousCompileContext);
compile.$$addScopeClass($compileNodes);
var namespace = null;
- return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn, futureParentElement) {
+ return function publicLinkFn(scope, cloneConnectFn, options) {
assertArg(scope, 'scope');
+
+ options = options || {};
+ var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
+ transcludeControllers = options.transcludeControllers,
+ futureParentElement = options.futureParentElement;
+
+ // When `parentBoundTranscludeFn` is passed, it is a
+ // `controllersBoundTransclude` function (it was previously passed
+ // as `transclude` to directive.link) so we must unwrap it to get
+ // its `boundTranscludeFn`
+ if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
+ parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
+ }
+
if (!namespace) {
namespace = detectNamespaceForChildElements(futureParentElement);
}
@@ -1326,7 +1360,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
transcludedScope.$$transcluded = true;
}
- return transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement);
+ return transcludeFn(transcludedScope, cloneFn, {
+ parentBoundTranscludeFn: previousBoundTranscludeFn,
+ transcludeControllers: controllers,
+ futureParentElement: futureParentElement
+ });
};
return boundTranscludeFn;
@@ -1794,7 +1832,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
isolateScope = scope.$new(true);
}
- transcludeFn = boundTranscludeFn && controllersBoundTransclude;
+ if (boundTranscludeFn) {
+ // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
+ // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
+ transcludeFn = controllersBoundTransclude;
+ transcludeFn.$$boundTransclude = boundTranscludeFn;
+ }
+
if (controllerDirectives) {
// TODO: merge `controllers` and `elementControllers` into single object.
controllers = {};
diff --git a/src/ng/directive/ngInclude.js b/src/ng/directive/ngInclude.js
index ceb0ee3368a9..e8634993c440 100644
--- a/src/ng/directive/ngInclude.js
+++ b/src/ng/directive/ngInclude.js
@@ -284,7 +284,7 @@ var ngIncludeFillContentDirective = ['$compile',
$compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
function namespaceAdaptedClone(clone) {
$element.append(clone);
- }, undefined, undefined, $element);
+ }, {futureParentElement: $element});
return;
}
diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js
index 294140bdd8e1..39e0a8c1e109 100755
--- a/test/ng/compileSpec.js
+++ b/test/ng/compileSpec.js
@@ -5209,6 +5209,100 @@ describe('$compile', function() {
});
+ // see issue https://github.com/angular/angular.js/issues/9413
+ describe('passing a parent bound transclude function to the link ' +
+ 'function returned from `$compile`', function() {
+
+ beforeEach(module(function() {
+ directive('lazyCompile', function($compile) {
+ return {
+ compile: function(tElement, tAttrs) {
+ var content = tElement.contents();
+ tElement.empty();
+ return function(scope, element, attrs, ctrls, transcludeFn) {
+ element.append(content);
+ $compile(content)(scope, undefined, {
+ parentBoundTranscludeFn: transcludeFn
+ });
+ };
+ }
+ };
+ });
+ directive('toggle', valueFn({
+ scope: {t: '=toggle'},
+ transclude: true,
+ template: ''
+ }));
+ }));
+
+ it('should preserve the bound scope', function() {
+
+ inject(function($compile, $rootScope) {
+ element = $compile(
+ '' +
+ '
' +
+ '
' +
+ 'SuccessError' +
+ '
' +
+ '
')($rootScope);
+
+ $rootScope.$apply('t = false');
+ expect($rootScope.$countChildScopes()).toBe(1);
+ expect(element.text()).toBe('');
+
+ $rootScope.$apply('t = true');
+ expect($rootScope.$countChildScopes()).toBe(4);
+ expect(element.text()).toBe('Success');
+
+ $rootScope.$apply('t = false');
+ expect($rootScope.$countChildScopes()).toBe(1);
+ expect(element.text()).toBe('');
+
+ $rootScope.$apply('t = true');
+ expect($rootScope.$countChildScopes()).toBe(4);
+ expect(element.text()).toBe('Success');
+ });
+ });
+
+
+ it('should preserve the bound scope when using recursive transclusion', function() {
+
+ directive('recursiveTransclude', valueFn({
+ transclude: true,
+ template: ''
+ }));
+
+ inject(function($compile, $rootScope) {
+ element = $compile(
+ '' +
+ '
' +
+ '
' +
+ '
' +
+ 'SuccessError' +
+ '
' +
+ '
' +
+ '
')($rootScope);
+
+ $rootScope.$apply('t = false');
+ expect($rootScope.$countChildScopes()).toBe(1);
+ expect(element.text()).toBe('');
+
+ $rootScope.$apply('t = true');
+ expect($rootScope.$countChildScopes()).toBe(4);
+ expect(element.text()).toBe('Success');
+
+ $rootScope.$apply('t = false');
+ expect($rootScope.$countChildScopes()).toBe(1);
+ expect(element.text()).toBe('');
+
+ $rootScope.$apply('t = true');
+ expect($rootScope.$countChildScopes()).toBe(4);
+ expect(element.text()).toBe('Success');
+ });
+ });
+ });
+
+
// see issue https://github.com/angular/angular.js/issues/9095
describe('removing a transcluded element', function() {