Skip to content

Commit c36fc03

Browse files
committed
fix($compile): always instantiate controllers in parent->child order
Previously it was possible to get into a situation where child controller was being instantiated before parent which resulted in an error. Closes angular#2738
1 parent 6f4d146 commit c36fc03

File tree

2 files changed

+94
-3
lines changed

2 files changed

+94
-3
lines changed

src/ng/directive/ngTransclude.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,14 @@
4949
*
5050
*/
5151
var ngTranscludeDirective = ngDirective({
52-
controller: ['$transclude', '$element', function($transclude, $element) {
53-
$transclude(function(clone) {
54-
$element.append(clone);
52+
controller: ['$transclude', '$element', '$scope', function($transclude, $element, $scope) {
53+
// use evalAsync so that we don't process transclusion before directives on the parent element even when the
54+
// transclusion replaces the current element. (we can't use prority here because that applies only to compile fns
55+
// and not controllers
56+
$scope.$evalAsync(function() {
57+
$transclude(function(clone) {
58+
$element.append(clone);
59+
});
5560
});
5661
}]
5762
});

test/ng/compileSpec.js

+86
Original file line numberDiff line numberDiff line change
@@ -2331,6 +2331,92 @@ describe('$compile', function() {
23312331
expect(asyncCtrlSpy).toHaveBeenCalledOnce();
23322332
});
23332333
});
2334+
2335+
2336+
2337+
it('should instantiate controllers in the parent->child order when transluction, templateUrl and replacement ' +
2338+
'are in the mix', function() {
2339+
// when a child controller is in the transclusion that replaces the parent element that has a directive with
2340+
// a controller, we should ensure that we first instantiate the parent and only then stuff that comes from the
2341+
// transclusion.
2342+
2343+
module(function() {
2344+
directive('parentDirective', function() {
2345+
return {
2346+
transclude: true,
2347+
replace: true,
2348+
templateUrl: 'parentDirective.html',
2349+
controller: function (log) { log('parentController'); }
2350+
};
2351+
});
2352+
directive('childDirective', function() {
2353+
return {
2354+
require: '^parentDirective',
2355+
templateUrl: 'childDirective.html',
2356+
controller : function(log) { log('childController'); }
2357+
};
2358+
});
2359+
});
2360+
2361+
inject(function($templateCache, log, $compile, $rootScope) {
2362+
$templateCache.put('parentDirective.html', '<div ng-transclude>parentTemplateText;</div>');
2363+
$templateCache.put('childDirective.html', '<span>childTemplateText;</span>');
2364+
2365+
element = $compile('<div parent-directive><div child-directive></div>childContentText;</div>')($rootScope);
2366+
$rootScope.$apply();
2367+
expect(log).toEqual('parentController; childController');
2368+
expect(element.text()).toBe('parentTemplateText;childTemplateText;childContentText;')
2369+
});
2370+
});
2371+
2372+
2373+
it('should instantiate controllers in the parent->child->baby order when nested transluction, templateUrl and ' +
2374+
'replacement are in the mix', function() {
2375+
// similar to the test above, except that we have one more layer of nesting and nested transclusion
2376+
2377+
module(function() {
2378+
directive('parentDirective', function() {
2379+
return {
2380+
transclude: true,
2381+
replace: true,
2382+
templateUrl: 'parentDirective.html',
2383+
controller: function (log) { log('parentController'); }
2384+
};
2385+
});
2386+
directive('childDirective', function() {
2387+
return {
2388+
require: '^parentDirective',
2389+
transclude: true,
2390+
replace: true,
2391+
templateUrl: 'childDirective.html',
2392+
controller : function(log) { log('childController'); }
2393+
};
2394+
});
2395+
directive('babyDirective', function() {
2396+
return {
2397+
require: '^childDirective',
2398+
templateUrl: 'babyDirective.html',
2399+
controller : function(log) { log('babyController'); }
2400+
};
2401+
});
2402+
});
2403+
2404+
inject(function($templateCache, log, $compile, $rootScope) {
2405+
$templateCache.put('parentDirective.html', '<div ng-transclude>parentTemplateText;</div>');
2406+
$templateCache.put('childDirective.html', '<span ng-transclude>childTemplateText;</span>');
2407+
$templateCache.put('babyDirective.html', '<span>babyTemplateText;</span>');
2408+
2409+
element = $compile('<div parent-directive>' +
2410+
'<div child-directive>' +
2411+
'childContentText;' +
2412+
'<div baby-directive>babyContent;</div>' +
2413+
'</div>' +
2414+
'</div>')($rootScope);
2415+
$rootScope.$apply();
2416+
expect(log).toEqual('parentController; childController; babyController');
2417+
expect(element.text()).toBe('parentTemplateText;childTemplateText;childContentText;babyTemplateText;')
2418+
});
2419+
});
23342420
});
23352421

23362422

0 commit comments

Comments
 (0)