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
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -1967,13 +1967,15 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
for (i in elementControllers) {
controller = elementControllers[i];
var controllerResult = controller();
if (controllerResult !== controller.instance &&
controller === controllerForBindings) {
// Remove and re-install bindToController bindings
thisLinkFn.$$destroyBindings();
thisLinkFn.$$destroyBindings =
initializeDirectiveBindings(scope, attrs, controllerResult,
bindings, scopeDirective);
if (controllerResult !== controller.instance) {
controller.instance = controllerResult;
$element.data('$' + directive.name + 'Controller', controllerResult);
Copy link
Contributor

Choose a reason for hiding this comment

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

there are a few different circumstances that this happens, so we might need to be a bit more careful about this --- be sure to include test coverage that covers controllers for transclude directives too

if (controller === controllerForBindings) {
// Remove and re-install bindToController bindings
thisLinkFn.$$destroyBindings();
thisLinkFn.$$destroyBindings =
initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective);
}
}
}
}
Expand Down
125 changes: 125 additions & 0 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4197,6 +4197,50 @@ describe('$compile', function() {
});


it('should respect controller return value', function() {
module(function() {
directive('logControllerProp', function(log) {
return {
controller: function($scope) {
this.foo = 'baz'; // value should not be used.
return {foo: 'bar'};
},
link: function(scope, element, attrs, controller) {
log(controller.foo);
}
};
});
});
inject(function(log, $compile, $rootScope) {
element = $compile('<log-controller-prop></log-controller-prop>')($rootScope);
expect(log).toEqual('bar');
expect(element.data('$logControllerPropController').foo).toEqual('bar');
});
});


it('primitive controller return values are ignored', function() {
module(function() {
directive('logControllerProp', function(log) {
return {
controller: function($scope) {
this.foo = 'baz'; // value *will* be used.
return 'bar';
},
link: function(scope, element, attrs, controller) {
log(controller.foo);
}
};
});
});
inject(function(log, $compile, $rootScope) {
element = $compile('<log-controller-prop></log-controller-prop>')($rootScope);
expect(log).toEqual('baz');
expect(element.data('$logControllerPropController').foo).toEqual('baz');
});
});


it('should get required parent controller', function() {
module(function() {
directive('nested', function(log) {
Expand All @@ -4216,6 +4260,28 @@ describe('$compile', function() {
});


it('should get explicit return value of required parent controller', function() {
module(function() {
directive('nested', function(log) {
return {
require: '^^?nested',
controller: function() {
return {foo: 'bar'};
},
link: function(scope, element, attrs, controller) {
log(!!controller && controller.foo);
}
};
});
});
inject(function(log, $compile, $rootScope) {
element = $compile('<div nested><div nested></div></div>')($rootScope);

expect(log).toEqual('bar; false');
});
});


it('should get required parent controller when the question mark precedes the ^^', function() {
module(function() {
directive('nested', function(log) {
Expand Down Expand Up @@ -4524,6 +4590,30 @@ describe('$compile', function() {
});


it('should respect explicit 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:');
});
});


it('should support controller alias', function() {
module(function($controllerProvider) {
$controllerProvider.register('MainCtrl', function() {
Expand Down Expand Up @@ -5525,6 +5615,41 @@ describe('$compile', function() {
});
});

it('should copy the explicit return value to all clones', function() {
module(function() {
directive('makeThree', valueFn({
transclude: 'content',
controller: function($transclude) {
this.foo = 'baz';
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.

ctrl.transclude(cloneAttach);
ctrl.transclude(cloneAttach);

function cloneAttach(clone) {
el.append(clone);
}
}
}));

directive('nested', function(log) {
return {
require: '^^makeThree',
link: function(scope, element, attrs, controller) {
log(controller.foo);
}
};
});
});
inject(function(log, $compile) {
element = $compile('<div make-three><div nested></div></div>')($rootScope);
$rootScope.$apply();
expect(log).toEqual('bar; bar; bar');
});
});

it('should provide the $transclude controller local as 5th argument to the pre and post-link function', function() {
var ctrlTransclude, preLinkTransclude, postLinkTransclude;
module(function() {
Expand Down