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

fix($compile): do not rebind parent bound transclude functions #9605

Closed

Conversation

dlongley
Copy link
Contributor

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 #9413

@mary-poppins
Copy link

I'm sorry, but I wasn't able to verify your Contributor License Agreement (CLA) signature. CLA signature is required for any code contributions to AngularJS.

Please sign our CLA and ensure that the CLA signature email address and the email address in this PR's commits match.

If you signed the CLA as a corporation, please let us know the company's name.

Thanks a bunch!

PS: If you signed the CLA in the past then most likely the email addresses don't match. Please sign the CLA again or update the email address in the commit of this PR.
PS2: If you are a Googler, please sign the CLA as well to simplify the CLA verification process.

@@ -1953,7 +1953,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var transcludeControllers;

// No scope passed in:
if (!isScope(scope)) {
if (!isScope(scope) && !isUndefined(scope)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this when we are not wrapping anything?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's still necessary, but not for the reason I originally thought. Let me take a closer look.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there may be a recursion case that its handling where there's still wrapping going on. I'll take another look when I get a chance.

@tbosch
Copy link
Contributor

tbosch commented Oct 14, 2014

Could you also change the docs for $compile:

  1. deprecate the transcludeFn that can be passed into $compile
  2. add transcludeFn in resulting linking function of calling $compile

@tbosch
Copy link
Contributor

tbosch commented Oct 14, 2014

Btw, the transcludeFn that is passed into the compile function of directives has been deprecated with the following explanation that you can use for the deprecation explanation above as well:

Note: The transclude function that is passed to the compile function is deprecated, as it e.g. does not know about the right outer scope. Please use the transclude function that is passed to the link function instead.

@dlongley
Copy link
Contributor Author

Btw, I signed the CLA with an account that lists the same email address as my github account, so I'm not sure why mary-poppins is complaining. It isn't the primary email address, but it is listed.

@dlongley
Copy link
Contributor Author

@tbosch, I was thinking that deprecating the transcludeFn for $compile and adding it for the resulting linking function would be done in another PR that exposes the new public API. That new PR would also switch the publicLinkFn to use an options hash parameter. I thought it would be cleaner to do that way, but, if you'd prefer, I can include it in this PR.

@dlongley dlongley force-pushed the fix-rebound-transcluded-scope branch from 3859ea0 to 4cdb797 Compare October 14, 2014 07:09
@dlongley
Copy link
Contributor Author

Fixed the last case of wrapping ... there was some tricky recursion that wasn't handled before.

@lgalfaso
Copy link
Contributor

@dlongley I see that you updated the PR for a fix but you did not added/modified any of the existing tests. Can you please add a test that shows this recursion case that you are talking about

@dlongley dlongley force-pushed the fix-rebound-transcluded-scope branch from 4cdb797 to 1011393 Compare October 14, 2014 13:47
@dlongley
Copy link
Contributor Author

@lgalfaso, done.

@dlongley
Copy link
Contributor Author

@tbosch, if you'd prefer me to add a commit to this PR with the doc change, I'll do so -- and also use the options parameter design discussed in #9579. Otherwise I can do that in another PR.

// a `controllersBoundTransclude` function, so prevent double-wrapping
// by using the previously-wrapped boundTranscludeFn.
var unwrappedFn = boundTranscludeFn.$$boundTransclude || boundTranscludeFn;
controllersBoundTransclude.$$boundTransclude = unwrappedFn;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This second assign for $$boundTransclude is not needed as we already did it above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixing this.

@tbosch
Copy link
Contributor

tbosch commented Oct 14, 2014

Could you just add a second commit to this PR that adds the options hash parameter to the publicLinkFn and also adds the documentation changes?

@@ -5124,6 +5124,122 @@ describe('$compile', function() {
});


// see issue https://github.com/angular/angular.js/issues/9413
describe('passing a parent bound transclude function to the link ' +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please think about how to simplify this test so that it only tests the changed behavior and is easier to understand?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is just a grouping for the two tests nested in it that are both related to passing a parent bound transclude function. Is that not a good summary title for those two tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following the example below ("removing a transcluded element") that then has nested tests below it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I did not mean the test suite but the tests themselves.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E.g. do we need a directive with terminal and priority and another directive with isolate scope to test this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, terminal, priority, and the isolate scope have been removed. The recursive directive test was added per @lgalfaso's request.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And do we really need 2 directives to test this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can reuse the toggle directive (instead of the recursive directive), but that may end up being less clear with double-toggling. What's being tested is recursive transclusion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about this for a bit. I think the second directive is necessary to make clear what's being tested. This test is specifically for recursive transclusion, not for directive recursion.

@tbosch
Copy link
Contributor

tbosch commented Oct 14, 2014

Sorry about the CLA hickup, our process should verify you soon.

@dlongley
Copy link
Contributor Author

Could you just add a second commit to this PR that adds the options hash parameter to the publicLinkFn and also adds the documentation changes?

Yeah, will do.

@dlongley dlongley force-pushed the fix-rebound-transcluded-scope branch from 1011393 to 83857c3 Compare October 14, 2014 16:59
@tbosch
Copy link
Contributor

tbosch commented Oct 15, 2014

Thanks. Sorry, but this will probably have to wait until after ngEurope (2 weeks)...

@dlongley
Copy link
Contributor Author

@tbosch, ok, thanks, no problem. Just wanted to be clear that I think it's ready when you guys are.

@dlongley
Copy link
Contributor Author

@tbosch -- I've sorted out the conflicts with this PR and it's ready for auto-merging again.

@dlongley
Copy link
Contributor Author

dlongley commented Nov 4, 2014

@tbosch, could this be merged now and make it into 1.3.2?

@caitp
Copy link
Contributor

caitp commented Nov 4, 2014

The fix itself looks good to me --- the tests are really long and hard to read (as noted in the discussion). They can be simplified at least a little bit once #9871 lands, so that the content of the tests is more focused on what is actually being tested.

Still need review from @tbosch though.

};
});
directive('toggle', function() {
return {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use valueFn({ ... your directive ... }) here to make it slightly smaller and easier to read (instead of function() { return { ... }; }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@dlongley dlongley force-pushed the fix-rebound-transcluded-scope branch from 98f65da to 1b567f5 Compare November 4, 2014 18:58
@petebacondarwin petebacondarwin modified the milestones: 1.3.x, Backlog Nov 4, 2014
@caitp caitp modified the milestones: 1.3.2, 1.3.x Nov 4, 2014
@petebacondarwin
Copy link
Contributor

@caitp - do you think this is ready to merge? @tbosch can you review?
If not sure then we should leave it in Milestone 1.3.x and look at it next week. I would like to limit what we need to achieve in 1.3.2.

@caitp
Copy link
Contributor

caitp commented Nov 5, 2014

i said before, the fix looks good to me, but should probably get approval from tbosch

children = children.concat(getChildScopes(childScope));
} while ((childScope = childScope.$$nextSibling));
return children;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

countScopes() and getChildScopes() are already available in this file. I will move the other copy and remove these.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Igor is adding these to ngMock --- so I wouldn't worry about it. We should land Igor's PR for that though

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Landed #9871 --- please just use (Scope.$countChildScopes() + 1) now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@dlongley dlongley force-pushed the fix-rebound-transcluded-scope branch from 1b567f5 to bb4e94f Compare November 5, 2014 18:42
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
Use `options` object hash style parameter for publicLinkFn. Expose
`options` parameter as part of the public API.

Closes angular#9413
@dlongley dlongley force-pushed the fix-rebound-transcluded-scope branch from bb4e94f to ff06bbd Compare November 5, 2014 18:43
@dlongley
Copy link
Contributor Author

dlongley commented Nov 5, 2014

Uses Scope.$countChildScopes from #9871 now.

@petebacondarwin
Copy link
Contributor

Gotcha!

@caitp
Copy link
Contributor

caitp commented Nov 5, 2014

alright, I don't think the tests are really getting any simpler, which is too bad, but it's hard to do that with the compiler. LGTM

@petebacondarwin
Copy link
Contributor

Merging.

@petebacondarwin
Copy link
Contributor

I squashed the two commits into one because the first actually fails without the second and it was too complicated to try to work out which bits needed to be moved between the commits to get them both to pass. The change to the new ngMock helpers, I put into a separate commit.

@dlongley
Copy link
Contributor Author

dlongley commented Nov 5, 2014

Awesome! Thanks, everyone.

@petebacondarwin
Copy link
Contributor

Landed as 841c090

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants