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

Commit 683fd71

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 3591ae0 commit 683fd71

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
@@ -2216,6 +2216,95 @@ describe('$compile', function() {
22162216
expect(asyncCtrlSpy).toHaveBeenCalledOnce();
22172217
});
22182218
});
2219+
2220+
2221+
2222+
it('should instantiate controllers in the parent->child order when transluction, templateUrl and replacement ' +
2223+
'are in the mix', function() {
2224+
// When a child controller is in the transclusion that replaces the parent element that has a directive with
2225+
// a controller, we should ensure that we first instantiate the parent and only then stuff that comes from the
2226+
// transclusion.
2227+
//
2228+
// The transclusion moves the child controller onto the same element as parent controller so both controllers are
2229+
// on the same level.
2230+
2231+
module(function() {
2232+
directive('parentDirective', function() {
2233+
return {
2234+
transclude: true,
2235+
replace: true,
2236+
templateUrl: 'parentDirective.html',
2237+
controller: function (log) { log('parentController'); }
2238+
};
2239+
});
2240+
directive('childDirective', function() {
2241+
return {
2242+
require: '^parentDirective',
2243+
templateUrl: 'childDirective.html',
2244+
controller : function(log) { log('childController'); }
2245+
};
2246+
});
2247+
});
2248+
2249+
inject(function($templateCache, log, $compile, $rootScope) {
2250+
$templateCache.put('parentDirective.html', '<div ng-transclude>parentTemplateText;</div>');
2251+
$templateCache.put('childDirective.html', '<span>childTemplateText;</span>');
2252+
2253+
element = $compile('<div parent-directive><div child-directive></div>childContentText;</div>')($rootScope);
2254+
$rootScope.$apply();
2255+
expect(log).toEqual('parentController; childController');
2256+
expect(element.text()).toBe('parentTemplateText;childTemplateText;childContentText;')
2257+
});
2258+
});
2259+
2260+
2261+
it('should instantiate controllers in the parent->child->baby order when nested transluction, templateUrl and ' +
2262+
'replacement are in the mix', function() {
2263+
// similar to the test above, except that we have one more layer of nesting and nested transclusion
2264+
2265+
module(function() {
2266+
directive('parentDirective', function() {
2267+
return {
2268+
transclude: true,
2269+
replace: true,
2270+
templateUrl: 'parentDirective.html',
2271+
controller: function (log) { log('parentController'); }
2272+
};
2273+
});
2274+
directive('childDirective', function() {
2275+
return {
2276+
require: '^parentDirective',
2277+
transclude: true,
2278+
replace: true,
2279+
templateUrl: 'childDirective.html',
2280+
controller : function(log) { log('childController'); }
2281+
};
2282+
});
2283+
directive('babyDirective', function() {
2284+
return {
2285+
require: '^childDirective',
2286+
templateUrl: 'babyDirective.html',
2287+
controller : function(log) { log('babyController'); }
2288+
};
2289+
});
2290+
});
2291+
2292+
inject(function($templateCache, log, $compile, $rootScope) {
2293+
$templateCache.put('parentDirective.html', '<div ng-transclude>parentTemplateText;</div>');
2294+
$templateCache.put('childDirective.html', '<span ng-transclude>childTemplateText;</span>');
2295+
$templateCache.put('babyDirective.html', '<span>babyTemplateText;</span>');
2296+
2297+
element = $compile('<div parent-directive>' +
2298+
'<div child-directive>' +
2299+
'childContentText;' +
2300+
'<div baby-directive>babyContent;</div>' +
2301+
'</div>' +
2302+
'</div>')($rootScope);
2303+
$rootScope.$apply();
2304+
expect(log).toEqual('parentController; childController; babyController');
2305+
expect(element.text()).toBe('parentTemplateText;childTemplateText;childContentText;babyTemplateText;')
2306+
});
2307+
});
22192308
});
22202309

22212310

0 commit comments

Comments
 (0)