Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Wrong scope used after passing transclude function to $compile #9413

Closed
dlongley opened this issue Oct 3, 2014 · 26 comments
Closed

Wrong scope used after passing transclude function to $compile #9413

dlongley opened this issue Oct 3, 2014 · 26 comments

Comments

@dlongley
Copy link
Contributor

dlongley commented Oct 3, 2014

I've run into something unexpected as demonstrated in this plunker. Specifically, I believe that transcluded elements are being linked to the wrong scope after a manual call to $compile that passes a transclude function. If the transclude function is called on its own -- the cloned elements are linked to the proper scope -- if it's passed to $compile and then the resulting link function is called, the wrong scope is used.

The plunker creates two directives, "lazy-compile" that lazy compiles its contents when a certain "trigger" variable is made truthy. It does this by, during its compilation phase, removing its contents and storing them in the template cache (so there's only ever one copy). Then, it sets up a bind-once watch for the trigger variable. Once the variable is true, the contents are fetched from the cache, cloned, re-appended to the element, and then $compile is called on them, passing any transclude function, if defined.

The second directive, "uses-transclude", is just a widget that uses transclusion and the lazy compilation directive in its template.

On the main page, when a link is clicked, the lazy compilation is triggered. This works properly in the plunker -- but only because of an ugly hack that you can see in script.js. The code there has some comments about what's going on. Essentially, if $compile is called and the transclude function is passed to it, when you run the resulting link function the transcluded elements are linked to the wrong scope. However, if you call the transclude function directly -- the cloned elements are linked to the proper scope. I would expect the same scope to be used in both cases. So, to resolve this problem, the lazy compile directive calls the transclude function itself, saves the scope's parent (prototypical parent, not $parent) from the cloned elements, and then rewrites the transclude function to ensure this same parent scope is used when the transclude function is called later in $compile.

The plunker prints out the "wrong scope" and the "right scope" via console.log once the compilation link is clicked.

Why are the scopes not the same when doing $compile(el, transcludeFn)(scope) vs. transcludeFn(function(clone) {})? Shouldn't the proper scope travel along with the transclude function closure and always be used regardless of where the content is inserted?

@lgalfaso
Copy link
Contributor

lgalfaso commented Oct 9, 2014

@dlongley I am aware of the fact that for transcluded functions, a scope may inherit from a scope that is not it's parent. Now, I looked at the plunker and I am still not able to understand what the specific issue is. In this example, if I change the version of angular to use 1.3-rc1, it shows the same result. Would it be possible for you to update the plunker so it shows the specific issue that rc4 broke?

@jeffbcross
Copy link
Contributor

+1 what @lgalfaso said. A failing unit test would be even better.

Possibly related to this commit (with breaking changes)? fb0c77f

@dlongley
Copy link
Contributor Author

dlongley commented Oct 9, 2014

@lgalfaso, sorry for the confusion; rc4 didn't break this, it's been broken for some time -- I'm unaware of how far back it goes. I've had this code around since before rc4 and I just had notes in there because I changed it to comply with changes to rc4. The problem persists. I'll make some changes to the plunker to indicate the problem a little bit better shortly.

@dlongley
Copy link
Contributor Author

dlongley commented Oct 9, 2014

@lgalfaso -- I've updated the plunker: http://plnkr.co/edit/LTAbdvt1Az1mG362yFy3?p=preview

There are two links to click now:

The first uses the default angular implementation where the transclude function is passed to $compile as expected. This results in the wrong scope (its prototypical parent is incorrect) getting linked to the transcluded elements.

The second includes the patch (a hack really) in the plunker to use the proper scope. It does this by calling the transclude function manually and keeping track of the proper prototypical parent scope and then rewrites the transclude function to use this prototypical parent instead. Angular should be using this parent (prototypical) and, it does, if you call the transclude function manually, but it doesn't if you pass the transclude function to $compile.

The code switches on whether or not a "fix" attribute is set to "true" (see snippet below). The transclude function is rewritten in the patch/hack to use the proper scope.

if(attrs.fix === 'true') {
  transcludeFn = fixTranscludeScope(transcludeFn);
}

// compile cached template and link
$compile(el, transcludeFn)(scope);

@tbosch tbosch self-assigned this Oct 9, 2014
@tbosch tbosch added this to the Backlog milestone Oct 9, 2014
@tbosch tbosch removed their assignment Oct 9, 2014
@dlongley
Copy link
Contributor Author

@lgalfaso, @tbosch:

It looks like the transclude function passed to $compile ends up getting passed to createBoundTranscludeFn on line 1303 of compile.js. This occurs when there is no parentBoundTranscludeFn but a transcludeFn has been passed (correct behavior, I think). However, the passed transclude function then gets re-wrapped (it has already been wrapped) and bound to the wrong scope via createBoundTranscludeFn.

The createBoundTranscludeFn function (defined on line 1318) is expecting a transclude function with a different signature. The transclude function passed to $compile is a controllersBoundTransclude function (it has already been bound to a scope and related controllers, etc). and has a signature of function(angular.Scope, cloneAttachFn, parentElement).

The transclude function that is passed is called instead with five parameters (transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement). This matches the publicLinkFn signature (which I presume is the kind of function createBoundTranscludeFn is expecting). Note that there's no argument swapping/manipulation code to deal with these differences; it just appears to be incorrect usage, especially given how the bound behavior might change.

So it looks like several things are going wrong in this particular case that are related to one another:

  1. The compiler is mistakenly re-binding a transclude function that is already properly bound.
  2. The compiler is passing the transclude function around incorrectly; it is treating it like a publicLinkFn, when it is actually a function that has already been bound to a scope and to controllers (which is the process a publicLinkFn goes through at some point).
  3. This results in the transclude function getting called with a transcluded scope that inherits from the wrong prototypical parent. When the transclude function is called, it has been wrapped in a boundTranscludeFn that is given no transcludedScope which causes it to inherit from the bound scope (which is now incorrect and does not match the previously bound scope).
  4. The already-bound transclude function has no way to receive the containingScope -- so when creating a transcluded scope as a prototypical child of its bound scope, the new scope will leak.

With this information, I tried a different hack (from the plunker) to resolve the problem (and to avoid having to call the transcludeFn manually to find the scope):

// rewrite transclude to destroy and replace invalid bound transcluded scope
transcludeFn = function(scope, cloneAttachFn, futureParentElement) {
  scope.$destroy();
  return transcludeFn(cloneAttachFn, futureParentElement);
};
// now compile
$compile(scope, transcludeFn);

Which will cause the right scope to be used, but results in a number of other problems because the function arguments don't match properly. For example, as mentioned, the containingScope won't be passed which will result in a memory leak.

Instead, it seems like the compiler should be able to detect whether the transcludeFn is already bound -- and it should be able to specify which containingScope should be used instead. There are a variety of different ways to change the code to make this happen; I'm not sure which is the most appropriate.

@dlongley
Copy link
Contributor Author

If the above diagnosis of the problem is accurate, one of the causes of the confusion may be that the variable name transcludeFn seems to be used regardless of the type of "transclude function" (bound/controllersBound/publicLink/etc). If true, and they can't all be treated the same way, it may help to do some minor refactoring to use more context-specific names.

@lgalfaso
Copy link
Contributor

It took me a few hours to understand it all, here is a simplified version of the original plunker http://plnkr.co/edit/tWYU90uaRgkvmP9Y5ndG?p=preview
I also created a test based on this example

it('should bind the tranclude function to the original scope when used ' +
    'in a future `$compile` call', function() {
  module(function() {
    directive('usesTransclude', function($compile) {
      return {
        scope: {},
        transclude: true,
        template: '<div><div ng-transclude></div></div>',
        compile: function(tElement, tAttrs) {
          var content = tElement.contents();
          tElement.empty();
          return function(scope, element, attrs, ctrls, transcludeFn) {
            element.append(content);
            $compile(content, transcludeFn)(scope);
          }
        }
      };
    });
  });
  inject(function($compile) {
    element = $compile(
        '<div>' +
          '<div ng-init="outer=true"></div>' +
          '<div uses-transclude>' +
            '<span ng-if="outer">Success</span><span ng-if="!outer">Error</span>' +
          '</div>' +
        '</div>')($rootScope);
    $rootScope.$digest();
    expect(element.text()).toBe('Success');
  });
});

and here is easy to see what the misconception is. The directive usesTransclude asks for an isolate scope, and then it continues the compilation on this new isolate scope (including the transclude function).

Now, this is not what you want to do as you want the parent scope (the one that is able to see outer) to be the scope that is used in the compilation. Remember that scope is isolated. The right way to do this would be for the $compile call to be done on the parent scope (the one that is able to see outer) this is $compile(content, transcludeFn)(scope.$parent);

With this change, there is no need for the fix to kick in.

With all this information, I am highly tempted to think that this issue is invalid.

@petebacondarwin WDYT?

@petebacondarwin
Copy link
Member

I believe that @dlongley's analysis is correct.

The problem is that the 2nd (transcludeFn) and 3rd (maxPriority) parameters of $compile are not really part of its public API. They are only there because $compile use recursion into itself to compile sub branches of the HTML. In order to do this it needs to pass down the current transclude function.

You can see that this transcludeFn gets used only by compileNodes, which states that its parameter is:

/*
 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
 *        scope argument is auto-generated to the new child of the transcluded parent scope.
 */

The compiler expects any transcludeFn that is passed in to be unbound, i.e. it is the transclusion from the parent of the current directive and so it will attach that scope. From the directive developers point of view we should only be calling $compile with a piece of HTML (or one or more DOM elements) that are to be compiled.

The question is whether this is a use case that we should be supporting. I was trying to think if there was another way of achieving this lazy compilation that @dlongley is trying to do but I cannot think of anything.

@IgorMinar and @tbosch - did you look at this? It seems to me that a change to the compiler would not be too hard to handle this case...

@robstolarz
Copy link

@petebacondarwin Lazy compilation is currently needed to implement recursive transcluding directives. I once wrote an issue about the lack of support for recursive transcluding directives here. I'd like either native recursion or lazy compilation to be implemented. It can be either because I'm not aware of another use of lazy compilation, I think people only use it to implement recursive transcluding directives.

@dlongley
Copy link
Contributor Author

@callmeStriking, another simple use case is performance. You may want to prevent or delay compilation of certain parts of a complex interface until a particular event is triggered.

@dlongley
Copy link
Contributor Author

@lgalfaso,

The right way to do this would be for the $compile call to be done on the parent scope (the one that is able to see outer) this is $compile(content, transcludeFn)(scope.$parent);

With this change, there is no need for the fix to kick in.

This seems like it would only work in a very specific-case; namely when the $parent of the lazy compile directive's scope happens to be the same one that the transcluded scope should inherit from. What if, for instance, there's another directive that wraps a lazy compile directive? Etc. It also doesn't consider that the lazy compile directive may want to bind some of the content to its scope -- and only the transcluded stuff will be bound to another scope.

lgalfaso added a commit to lgalfaso/angular.js that referenced this issue Oct 12, 2014
Do not double bound transclude functions that are passed to the
compiler that are already bounded

Closes angular#9413
@lgalfaso
Copy link
Contributor

Created a PR to have the discussion if the transcludeFn of $compile should be a public API or not

lgalfaso added a commit to lgalfaso/angular.js that referenced this issue Oct 12, 2014
Do not double bind transclude functions that are passed to the compiler
that were already bound

Closes angular#9413
lgalfaso added a commit to lgalfaso/angular.js that referenced this issue Oct 13, 2014
Do not double bind transclude functions that are passed to the compiler
that were already bound

Closes angular#9413
lgalfaso added a commit to lgalfaso/angular.js that referenced this issue Oct 13, 2014
Do not double bind transclude functions that are passed to the compiler that were already bound

Worked with @dlongley on the implementation

Closes angular#9413
@tbosch
Copy link
Contributor

tbosch commented Oct 13, 2014

Hi,
@IgorMinar and I looked into this and this is not a proper usage of the API:
The transcludeFn passed into $compile is not of the same kind as the one you get injected in the link function of a directive, as @lgalfaso noticed. The transcludeFn passed into $compile will always be called with an explicit scope created during this call to $compile, which is the wrong one for your use case.

However, there is a simpler workaround: Create the following wrapper around the transcludeFn that just removes the first argument (the scope argument):

newTranscludeFn = function() {
    var args = [].slice.call(arguments, 1);
    return transcludeFn.apply(this, args);
};

(see this punker: http://plnkr.co/edit/7quB43McPIaSo5YzguWo?p=preview)

@lgalfaso To support this use case without this wrapper, the correct way would be to pass the transcludeFn to the link function returned by $compile (as 4th argument, the parentBoundTranscludeFn, see https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L1156). However, this is not a public API and also this would require a little change to the controllersBoundTransclude (https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L1952) so that is allows the case of calling it with controllersBoundTransclude(undefined, cloneConnectFn)...

Closing this, but thanks for all the analyzing work!

@tbosch tbosch closed this as completed Oct 13, 2014
@dlongley
Copy link
Contributor Author

@tbosch, that workaround does not work -- I mentioned it earlier #9413 (comment). It leaks because of the new usage of containing scopes as $parents.

@tbosch
Copy link
Contributor

tbosch commented Oct 13, 2014

@dlongley Yes, you are right. But really, the right way to do this would be the way I explained above:
Pass the transcludeFn into the linking function.

So let's do the following:

  1. reorder the arguments of publicLinkFn (https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L1156) to be publicLinkFn(scope, cloneConnectFn, parentBoundTranscludeFn, transcludeControllers, ...) and document the third parameter also as part of the public API (right now only the first two arguments are public)
  2. change controllersBoundTransclude (https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L1956) to only the parameter shifting if scope && !isScope(scope)

Then your example would be fixed using

$compile(el)(scope.$parent, undefined, transcludeFn);

Reopening this.

Note: This is not a breaking change as only the first 2 arguments of the publicLinkFn are part of the public API so far...

@tbosch tbosch reopened this Oct 13, 2014
@tbosch
Copy link
Contributor

tbosch commented Oct 13, 2014

I.e. we can deal with this sometime later in 1.3...

@tbosch
Copy link
Contributor

tbosch commented Oct 13, 2014

To remove all of the wrapping (and also the problem with the containingScope), we should do the following:

  1. reorder the arguments of publicLinkFn (see above) and pass in the transcludeFn in the linking function
  2. add the boundTranscludeFn as a property to the controllersBoundTranscludeFn (e.g. $$boundTranscludeFn) and use that for the next wrapping into a new controllersBoundTranscludeFn.

So we would keep the original boundTranscludeFn from the previous compile and pass it into the nested compile's link function. I think this is better than #9579 as we don't just want to preserve the correct scope, but also the correct previousBoundTranscludeFn which the boundTranscludeFn also stores.

@dlongley Could you try this approach as well?

Note: As discussed in #9579 we might want to change the arguments of the publickLinkFn into the following: publicLinkFn(scope, cloneConnectFn, options) with options being a hash with the rest of the arguments.

@dlongley
Copy link
Contributor Author

@tbosch, sure, I'll give it a shot soon.

@dlongley
Copy link
Contributor Author

@tbosch -- I think it's working. No memory leaks either. I'm going to clean this up and if I can get the tests running, do a PR with these changes. This PR will not switch to using options for the publicLinkFn. I think that should be done as a separate issue that also updates the docs and officially makes this part of the public API.

dlongley added a commit to dlongley/angular.js that referenced this issue Oct 14, 2014
Do not rebind parent bound transclude functions passed to the
link function returned from `$compile`. Change parameter
order of ```publicLinkFn``` to allow parent bound transclude
functions to be passed (do not make public yet).

The current `$compile` public API documentation indicates that
a transclude function may be passed as a second parameter, but
it is not clear that this is not the same function that is given
to directive link functions as the `transcludeFn` parameter. This
parameter must be passed in order to implement, for example,
lazy compilation.

In order to pass the `transcludeFn` parameter given to
a directive's link function, it must be passed to the
result of a call to `$compile` (i.e. `publicLinkFn`). Doing so,
however, would cause two bugs. First, the passed transclude
function would get rebound, improperly changing its scope
from its original binding. Second, the `containingScope` would
not be updated and the wrong `$parent` would be assigned to
the `transcludedScope` resulting in a memory leak. This patch
fixes both of these issues.

This patch does not expose a new public API for `publicLinkFn`,
rather, it leaves this to a later change that better considers
how that public API should look.

Thanks to @lgalfaso, @tbosch, and @petebacondarwin.

Related to angular#9413
dlongley added a commit to dlongley/angular.js that referenced this issue Oct 14, 2014
Do not rebind parent bound transclude functions passed to the
link function returned from `$compile`. Change parameter
order of ```publicLinkFn``` to allow parent bound transclude
functions to be passed (do not make public yet).

The current `$compile` public API documentation indicates that
a transclude function may be passed as a second parameter, but
it is not clear that this is not the same function that is given
to directive link functions as the `transcludeFn` parameter. This
parameter must be passed in order to implement, for example,
lazy compilation.

In order to pass the `transcludeFn` parameter given to
a directive's link function, it must be passed to the
result of a call to `$compile` (i.e. `publicLinkFn`). Doing so,
however, would cause two bugs. First, the passed transclude
function would get rebound, improperly changing its scope
from its original binding. Second, the `containingScope` would
not be updated and the wrong `$parent` would be assigned to
the `transcludedScope` resulting in a memory leak. This patch
fixes both of these issues.

This patch does not expose a new public API for `publicLinkFn`,
rather, it leaves this to a later change that better considers
how that public API should look.

Thanks to @lgalfaso, @tbosch, and @petebacondarwin.

Related to angular#9413
dlongley added a commit to dlongley/angular.js that referenced this issue Oct 14, 2014
Do not rebind parent bound transclude functions passed to the
link function returned from `$compile`. Change parameter
order of ```publicLinkFn``` to allow parent bound transclude
functions to be passed (do not make public yet).

The current `$compile` public API documentation indicates that
a transclude function may be passed as a second parameter, but
it is not clear that this is not the same function that is given
to directive link functions as the `transcludeFn` parameter. This
parameter must be passed in order to implement, for example,
lazy compilation.

In order to pass the `transcludeFn` parameter given to
a directive's link function, it must be passed to the
result of a call to `$compile` (i.e. `publicLinkFn`). Doing so,
however, would cause two bugs. First, the passed transclude
function would get rebound, improperly changing its scope
from its original binding. Second, the `containingScope` would
not be updated and the wrong `$parent` would be assigned to
the `transcludedScope` resulting in a memory leak. This patch
fixes both of these issues.

This patch does not expose a new public API for `publicLinkFn`,
rather, it leaves this to a later change that better considers
how that public API should look.

Thanks to @lgalfaso, @tbosch, and @petebacondarwin.

Related to angular#9413
dlongley added a commit to dlongley/angular.js that referenced this issue Oct 14, 2014
Do not rebind parent bound transclude functions passed to the
link function returned from `$compile`. Change parameter
order of ```publicLinkFn``` to allow parent bound transclude
functions to be passed (do not make public yet).

The current `$compile` public API documentation indicates that
a transclude function may be passed as a second parameter, but
it is not clear that this is not the same function that is given
to directive link functions as the `transcludeFn` parameter. This
parameter must be passed in order to implement, for example,
lazy compilation.

In order to pass the `transcludeFn` parameter given to
a directive's link function, it must be passed to the
result of a call to `$compile` (i.e. `publicLinkFn`). Doing so,
however, would cause two bugs. First, the passed transclude
function would get rebound, improperly changing its scope
from its original binding. Second, the `containingScope` would
not be updated and the wrong `$parent` would be assigned to
the `transcludedScope` resulting in a memory leak. This patch
fixes both of these issues.

This patch does not expose a new public API for `publicLinkFn`,
rather, it leaves this to a later change that better considers
how that public API should look.

Thanks to @lgalfaso, @tbosch, and @petebacondarwin.

Related to angular#9413
dlongley added a commit to dlongley/angular.js that referenced this issue Oct 14, 2014
Use `options` object hash style parameter for publicLinkFn. Expose
`options` parameter as part of the public API.

Closes angular#9413.
dlongley added a commit to dlongley/angular.js that referenced this issue Oct 14, 2014
Do not rebind parent bound transclude functions passed to the
link function returned from `$compile`. Change parameter
order of ```publicLinkFn``` to allow parent bound transclude
functions to be passed (do not make public yet).

The current `$compile` public API documentation indicates that
a transclude function may be passed as a second parameter, but
it is not clear that this is not the same function that is given
to directive link functions as the `transcludeFn` parameter. This
parameter must be passed in order to implement, for example,
lazy compilation.

In order to pass the `transcludeFn` parameter given to
a directive's link function, it must be passed to the
result of a call to `$compile` (i.e. `publicLinkFn`). Doing so,
however, would cause two bugs. First, the passed transclude
function would get rebound, improperly changing its scope
from its original binding. Second, the `containingScope` would
not be updated and the wrong `$parent` would be assigned to
the `transcludedScope` resulting in a memory leak. This patch
fixes both of these issues.

This patch does not expose a new public API for `publicLinkFn`,
rather, it leaves this to a later change that better considers
how that public API should look.

Thanks to @lgalfaso, @tbosch, and @petebacondarwin.

Related to angular#9413
dlongley added a commit to dlongley/angular.js that referenced this issue Oct 14, 2014
Use `options` object hash style parameter for publicLinkFn. Expose
`options` parameter as part of the public API.

Closes angular#9413
@tbosch tbosch modified the milestones: Backlog, 1.3.1 Oct 15, 2014
dlongley added a commit to dlongley/angular.js that referenced this issue Oct 27, 2014
Do not rebind parent bound transclude functions passed to the
link function returned from `$compile`. Change parameter
order of ```publicLinkFn``` to allow parent bound transclude
functions to be passed (do not make public yet).

The current `$compile` public API documentation indicates that
a transclude function may be passed as a second parameter, but
it is not clear that this is not the same function that is given
to directive link functions as the `transcludeFn` parameter. This
parameter must be passed in order to implement, for example,
lazy compilation.

In order to pass the `transcludeFn` parameter given to
a directive's link function, it must be passed to the
result of a call to `$compile` (i.e. `publicLinkFn`). Doing so,
however, would cause two bugs. First, the passed transclude
function would get rebound, improperly changing its scope
from its original binding. Second, the `containingScope` would
not be updated and the wrong `$parent` would be assigned to
the `transcludedScope` resulting in a memory leak. This patch
fixes both of these issues.

This patch does not expose a new public API for `publicLinkFn`,
rather, it leaves this to a later change that better considers
how that public API should look.

Thanks to @lgalfaso, @tbosch, and @petebacondarwin.

Related to angular#9413
dlongley added a commit to dlongley/angular.js that referenced this issue Oct 27, 2014
Use `options` object hash style parameter for publicLinkFn. Expose
`options` parameter as part of the public API.

Closes angular#9413
dlongley added a commit to dlongley/angular.js that referenced this issue Nov 4, 2014
Do not rebind parent bound transclude functions passed to the
link function returned from `$compile`. Change parameter
order of ```publicLinkFn``` to allow parent bound transclude
functions to be passed (do not make public yet).

The current `$compile` public API documentation indicates that
a transclude function may be passed as a second parameter, but
it is not clear that this is not the same function that is given
to directive link functions as the `transcludeFn` parameter. This
parameter must be passed in order to implement, for example,
lazy compilation.

In order to pass the `transcludeFn` parameter given to
a directive's link function, it must be passed to the
result of a call to `$compile` (i.e. `publicLinkFn`). Doing so,
however, would cause two bugs. First, the passed transclude
function would get rebound, improperly changing its scope
from its original binding. Second, the `containingScope` would
not be updated and the wrong `$parent` would be assigned to
the `transcludedScope` resulting in a memory leak. This patch
fixes both of these issues.

This patch does not expose a new public API for `publicLinkFn`,
rather, it leaves this to a later change that better considers
how that public API should look.

Thanks to @lgalfaso, @tbosch, and @petebacondarwin.

Related to angular#9413
dlongley added a commit to dlongley/angular.js that referenced this issue Nov 4, 2014
Use `options` object hash style parameter for publicLinkFn. Expose
`options` parameter as part of the public API.

Closes angular#9413
dlongley added a commit to dlongley/angular.js that referenced this issue Nov 5, 2014
Do not rebind parent bound transclude functions passed to the
link function returned from `$compile`. Change parameter
order of ```publicLinkFn``` to allow parent bound transclude
functions to be passed (do not make public yet).

The current `$compile` public API documentation indicates that
a transclude function may be passed as a second parameter, but
it is not clear that this is not the same function that is given
to directive link functions as the `transcludeFn` parameter. This
parameter must be passed in order to implement, for example,
lazy compilation.

In order to pass the `transcludeFn` parameter given to
a directive's link function, it must be passed to the
result of a call to `$compile` (i.e. `publicLinkFn`). Doing so,
however, would cause two bugs. First, the passed transclude
function would get rebound, improperly changing its scope
from its original binding. Second, the `containingScope` would
not be updated and the wrong `$parent` would be assigned to
the `transcludedScope` resulting in a memory leak. This patch
fixes both of these issues.

This patch does not expose a new public API for `publicLinkFn`,
rather, it leaves this to a later change that better considers
how that public API should look.

Thanks to @lgalfaso, @tbosch, and @petebacondarwin.

Related to angular#9413
dlongley added a commit to dlongley/angular.js that referenced this issue Nov 5, 2014
Use `options` object hash style parameter for publicLinkFn. Expose
`options` parameter as part of the public API.

Closes angular#9413
dlongley added a commit to dlongley/angular.js that referenced this issue Nov 5, 2014
Do not rebind parent bound transclude functions passed to the
link function returned from `$compile`. Change parameter
order of ```publicLinkFn``` to allow parent bound transclude
functions to be passed (do not make public yet).

The current `$compile` public API documentation indicates that
a transclude function may be passed as a second parameter, but
it is not clear that this is not the same function that is given
to directive link functions as the `transcludeFn` parameter. This
parameter must be passed in order to implement, for example,
lazy compilation.

In order to pass the `transcludeFn` parameter given to
a directive's link function, it must be passed to the
result of a call to `$compile` (i.e. `publicLinkFn`). Doing so,
however, would cause two bugs. First, the passed transclude
function would get rebound, improperly changing its scope
from its original binding. Second, the `containingScope` would
not be updated and the wrong `$parent` would be assigned to
the `transcludedScope` resulting in a memory leak. This patch
fixes both of these issues.

This patch does not expose a new public API for `publicLinkFn`,
rather, it leaves this to a later change that better considers
how that public API should look.

Thanks to @lgalfaso, @tbosch, and @petebacondarwin.

Related to angular#9413
dlongley added a commit to dlongley/angular.js that referenced this issue Nov 5, 2014
Use `options` object hash style parameter for publicLinkFn. Expose
`options` parameter as part of the public API.

Closes angular#9413
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.