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

Commit 45f9f62

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 #2738
1 parent 3967f5f commit 45f9f62

File tree

2 files changed

+97
-3
lines changed

2 files changed

+97
-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 priority 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

+89
Original file line numberDiff line numberDiff line change
@@ -2331,6 +2331,95 @@ 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+
// The transclusion moves the child controller onto the same element as parent controller so both controllers are
2344+
// on the same level.
2345+
2346+
module(function() {
2347+
directive('parentDirective', function() {
2348+
return {
2349+
transclude: true,
2350+
replace: true,
2351+
templateUrl: 'parentDirective.html',
2352+
controller: function (log) { log('parentController'); }
2353+
};
2354+
});
2355+
directive('childDirective', function() {
2356+
return {
2357+
require: '^parentDirective',
2358+
templateUrl: 'childDirective.html',
2359+
controller : function(log) { log('childController'); }
2360+
};
2361+
});
2362+
});
2363+
2364+
inject(function($templateCache, log, $compile, $rootScope) {
2365+
$templateCache.put('parentDirective.html', '<div ng-transclude>parentTemplateText;</div>');
2366+
$templateCache.put('childDirective.html', '<span>childTemplateText;</span>');
2367+
2368+
element = $compile('<div parent-directive><div child-directive></div>childContentText;</div>')($rootScope);
2369+
$rootScope.$apply();
2370+
expect(log).toEqual('parentController; childController');
2371+
expect(element.text()).toBe('parentTemplateText;childTemplateText;childContentText;')
2372+
});
2373+
});
2374+
2375+
2376+
it('should instantiate controllers in the parent->child->baby order when nested transluction, templateUrl and ' +
2377+
'replacement are in the mix', function() {
2378+
// similar to the test above, except that we have one more layer of nesting and nested transclusion
2379+
2380+
module(function() {
2381+
directive('parentDirective', function() {
2382+
return {
2383+
transclude: true,
2384+
replace: true,
2385+
templateUrl: 'parentDirective.html',
2386+
controller: function (log) { log('parentController'); }
2387+
};
2388+
});
2389+
directive('childDirective', function() {
2390+
return {
2391+
require: '^parentDirective',
2392+
transclude: true,
2393+
replace: true,
2394+
templateUrl: 'childDirective.html',
2395+
controller : function(log) { log('childController'); }
2396+
};
2397+
});
2398+
directive('babyDirective', function() {
2399+
return {
2400+
require: '^childDirective',
2401+
templateUrl: 'babyDirective.html',
2402+
controller : function(log) { log('babyController'); }
2403+
};
2404+
});
2405+
});
2406+
2407+
inject(function($templateCache, log, $compile, $rootScope) {
2408+
$templateCache.put('parentDirective.html', '<div ng-transclude>parentTemplateText;</div>');
2409+
$templateCache.put('childDirective.html', '<span ng-transclude>childTemplateText;</span>');
2410+
$templateCache.put('babyDirective.html', '<span>babyTemplateText;</span>');
2411+
2412+
element = $compile('<div parent-directive>' +
2413+
'<div child-directive>' +
2414+
'childContentText;' +
2415+
'<div baby-directive>babyContent;</div>' +
2416+
'</div>' +
2417+
'</div>')($rootScope);
2418+
$rootScope.$apply();
2419+
expect(log).toEqual('parentController; childController; babyController');
2420+
expect(element.text()).toBe('parentTemplateText;childTemplateText;childContentText;babyTemplateText;')
2421+
});
2422+
});
23342423
});
23352424

23362425

0 commit comments

Comments
 (0)