-
Notifications
You must be signed in to change notification settings - Fork 27.4k
respect explicit return values from controller functions (fixes #11147) #11326
Conversation
failing test for angular#11147 Controllers instantiated for directives by the $compile service do not respect explicit return values from the controller function.
When controller functions return an explicit value that value should be what is passed to the linking functions, and to any child/sibling controllers that `require` it. It should also be bound to the data store on the dom element. Closes angular#11147
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project, in which case you'll need to sign a Contributor License Agreement (CLA) at https://cla.developers.google.com/. If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address. Check the information on your CLA or see this help article on setting the email on your git commits. Once you've done that, please reply here to let us know. If you signed the CLA as a corporation, please let us know the company's name. |
CLA updated |
CLAs look good, thanks! |
@caitp you have done some work on this part of the code. Can you take a look at this one? |
the only thing that's needed here is updating the element data. this is a real bug since it affects the behaviour of require'd directives :( |
// Remove and re-install bindToController bindings | ||
thisLinkFn.$$destroyBindings(); | ||
thisLinkFn.$$destroyBindings = | ||
if (controllerResult !== controller.instance && (isObject(controllerResult) || isFunction(controllerResult))) { |
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.
in particular, don't worry about the isObject() || isFunction()
stuff, since that's handled in controller.js anyways
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.
Nevermind
I'm not sure what you are asking for here. We should mimic the behavior of native javascript: If they return an object or function use that as the result of the instantiation, otherwise return "as normal":
function returnsStr() {
this.foo = "bar";
return "baz";
}
new returnsStr() === {foo:"bar"} // "baz" return value is ignored
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.
this code is duplicating code which already runs in the controller()
function itself, it's not needed here
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.
particularly here (the instantiate
object is not updated here, because the compiler needed to be able to determine if a new result was returned so that the bindings could be reset --- so you still need to set controller.instance
)
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.
this diff
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.
Nevermind,
I see what you are saying, my above concern is already handled by the call to the controller()
callback. I am adding a test case that verifies it.
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.
argh!
Come on Github! Real time updates for line comments!
LGTM with comments addressed |
unclear what this means
Are you asking me to delete the first of these two lines?: controller.instance = controllerResult;
$element.data('$' + directive.name + 'Controller', controllerResult); Both lines are required for my test to pass. |
no, you need that line as mentioned above --- I would like you to add additional tests, though.
|
your use of plural case(s) is sending me down a rabbit hole in I've added this one so far: it('should respect controller return value when using controllerAs', function() {
module(function() {
directive('main', function() {
return {
templateUrl: 'main.html',
transclude: true,
scope: {},
controller: function() {
this.name = 'lucas';
return {name: 'george'};
},
controllerAs: 'mainCtrl'
};
});
});
inject(function($templateCache, $compile, $rootScope) {
$templateCache.put('main.html', '<span>template:{{mainCtrl.name}} <div ng-transclude></div></span>');
element = $compile('<div main>transclude:{{mainCtrl.name}}</div>')($rootScope);
$rootScope.$apply();
expect(element.text()).toBe('template:george transclude:');
});
}); But there are plenty of other cases. You want me to go nuts and add dozens of test cases, or are there specific scenarios you want tested? |
you don't need "dozens" of tests, but add ones that deal with a directive whose controller gets the transclude function |
…values. Includes tests for controllers that: - explicitly return primitives - are attached to scope using `controllerAs` - transclude contents
The `controller()` callback already performs the required type checks and guarantees that `controller() !== controller.instance` in the event that the function returns an explicit value.
@caitp I believe I have addressed all of your concerns, but I am not familiar enough with the inner workings of transclude to be sure I have covered all cases. Please let me know if there is a scenario I have missed (pointers to existing tests would be helpful). |
return {transclude:$transclude, foo: 'bar'}; | ||
}, | ||
link: function(scope, el, attr, ctrl) { | ||
ctrl.transclude(cloneAttach); |
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.
we don't need to actually run the transclude fn, we just need to assert that it's there
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 would be good to make these test cases a little closer, so that the only difference between them is the presence of the transclude property of the DDO. almost there =)
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.
so proving each clone gets the correct object reference to the parent controller has no value? (serious question)
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.
no, this is tested elsewhere --- we only really care that the nested directive gets the right controller
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.
in my mind
"each clone gets the correct object reference to the parent controller" === "the nested directive gets the right controller"
.
I need to call $transclude
at least once to see what the nested controller gets, right?
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.
well to link the nested directive yes, but you don't need to call it 3 times --- an expect(controller).toBe(the right thing)
is the only real assertion that is needed
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.
understood.
on it.
@caitp, made most of the changes requested, except this one:
Can you clarify which tests you want to look closer? |
merged, thanks for the work |
awesome. Thank you! |
When controller functions return an explicit value that value should be what is passed to the linking functions, and to any child/sibling controllers that `require` it. It should also be bound to the data store on the dom element. Closes angular#11147 Closes angular#11326
When controller functions return an explicit value that value should be what is passed to the linking functions, and to any child/sibling controllers that
require
it. It should also be bound to the data store on the DOM element.Closes #11147