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

respect explicit return values from controller functions (fixes #11147) #11326

Closed
wants to merge 5 commits into from

Conversation

jamestalmage
Copy link
Contributor

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

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
@googlebot
Copy link

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.

@jamestalmage
Copy link
Contributor Author

CLA updated

@googlebot
Copy link

CLAs look good, thanks!

@petebacondarwin
Copy link
Contributor

@caitp you have done some work on this part of the code. Can you take a look at this one?

@caitp
Copy link
Contributor

caitp commented Mar 16, 2015

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))) {
Copy link
Contributor

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

Copy link
Contributor Author

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

Copy link
Contributor

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

Copy link
Contributor

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)

Choose a reason for hiding this comment

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

this diff

Copy link
Contributor Author

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.

Copy link
Contributor Author

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!

@caitp
Copy link
Contributor

caitp commented Mar 16, 2015

LGTM with comments addressed

@jamestalmage
Copy link
Contributor Author

unclear what this means

the only thing that's needed here is updating the element data

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.

@caitp
Copy link
Contributor

caitp commented Mar 16, 2015

no, you need that line as mentioned above --- I would like you to add additional tests, though.

  1. add another test where a controller which returns a custom object is require'd by another directive, assert that the directive gets the right controller (this is sort of what you're testing already, but it tests what we actually want, rather than the mechanics of it)
  2. add similar test cases which verify the transclude cases

@jamestalmage
Copy link
Contributor Author

`2. add similar test cases which verify the transclude cases

your use of plural case(s) is sending me down a rabbit hole in compileSpec. There are many tests related to transclusion and controllers.

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?

@caitp
Copy link
Contributor

caitp commented Mar 16, 2015

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.
@jamestalmage
Copy link
Contributor Author

@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);
Copy link
Contributor

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

Copy link
Contributor

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 =)

Copy link
Contributor Author

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)

Copy link
Contributor

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

Copy link
Contributor Author

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?

Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

understood.
on it.

jamestalmage added a commit to jamestalmage/angular.js that referenced this pull request Mar 16, 2015
@jamestalmage
Copy link
Contributor Author

@caitp, made most of the changes requested, except this one:

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

Can you clarify which tests you want to look closer?

@caitp
Copy link
Contributor

caitp commented Mar 17, 2015

merged, thanks for the work

@caitp caitp closed this in 9900610 Mar 17, 2015
@jamestalmage jamestalmage deleted the fix-11147 branch March 17, 2015 14:26
@jamestalmage
Copy link
Contributor Author

awesome. Thank you!

netman92 pushed a commit to netman92/angular.js that referenced this pull request Aug 8, 2015
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
netman92 pushed a commit to netman92/angular.js that referenced this pull request Aug 8, 2015
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.

Controller from require is empty when using an explicit return
5 participants