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

Commit 90f8707

Browse files
tboschvojtajina
authored andcommitted
fix($compile): accessing controllers of transcluded directives from children
Additional API (backwards compatible) - Injects `$transclude` (see directive controllers) as 5th argument to directive link functions. - `$transclude` takes an optional scope as first parameter that overrides the bound scope. Deprecations: - `transclude` parameter of directive compile functions (use the new parameter for link functions instead). Refactorings: - Don't use comment node to temporarily store controllers - `ngIf`, `ngRepeat`, ... now all use `$transclude` Closes #4935.
1 parent c785918 commit 90f8707

File tree

11 files changed

+434
-73
lines changed

11 files changed

+434
-73
lines changed

src/ng/compile.js

+94-47
Large diffs are not rendered by default.

src/ng/directive/ngIf.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,14 @@ var ngIfDirective = ['$animate', function($animate) {
8686
terminal: true,
8787
restrict: 'A',
8888
$$tlb: true,
89-
compile: function (element, attr, transclude) {
90-
return function ($scope, $element, $attr) {
89+
link: function ($scope, $element, $attr, ctrl, $transclude) {
9190
var block, childScope;
9291
$scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
9392

9493
if (toBoolean(value)) {
9594
if (!childScope) {
9695
childScope = $scope.$new();
97-
transclude(childScope, function (clone) {
96+
$transclude(childScope, function (clone) {
9897
block = {
9998
startNode: clone[0],
10099
endNode: clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ')
@@ -115,7 +114,6 @@ var ngIfDirective = ['$animate', function($animate) {
115114
}
116115
}
117116
});
118-
};
119117
}
120118
};
121119
}];

src/ng/directive/ngInclude.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,12 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
154154
priority: 400,
155155
terminal: true,
156156
transclude: 'element',
157-
compile: function(element, attr, transclusion) {
157+
compile: function(element, attr) {
158158
var srcExp = attr.ngInclude || attr.src,
159159
onloadExp = attr.onload || '',
160160
autoScrollExp = attr.autoscroll;
161161

162-
return function(scope, $element) {
162+
return function(scope, $element, $attr, ctrl, $transclude) {
163163
var changeCounter = 0,
164164
currentScope,
165165
currentElement;
@@ -188,7 +188,7 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
188188
if (thisChangeId !== changeCounter) return;
189189
var newScope = scope.$new();
190190

191-
transclusion(newScope, function(clone) {
191+
$transclude(newScope, function(clone) {
192192
cleanupLastIncludeContent();
193193

194194
currentScope = newScope;

src/ng/directive/ngRepeat.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
201201
priority: 1000,
202202
terminal: true,
203203
$$tlb: true,
204-
compile: function(element, attr, linker) {
205-
return function($scope, $element, $attr){
204+
link: function($scope, $element, $attr, ctrl, $transclude){
206205
var expression = $attr.ngRepeat;
207206
var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),
208207
trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn,
@@ -364,7 +363,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
364363
// jshint bitwise: true
365364

366365
if (!block.startNode) {
367-
linker(childScope, function(clone) {
366+
$transclude(childScope, function(clone) {
368367
clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' ');
369368
$animate.enter(clone, null, jqLite(previousNode));
370369
previousNode = clone;
@@ -377,7 +376,6 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
377376
}
378377
lastBlockMap = nextBlockMap;
379378
});
380-
};
381379
}
382380
};
383381
}];

src/ng/directive/ngSwitch.js

+7-9
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,10 @@ var ngSwitchWhenDirective = ngDirective({
160160
transclude: 'element',
161161
priority: 800,
162162
require: '^ngSwitch',
163-
compile: function(element, attrs, transclude) {
164-
return function(scope, element, attr, ctrl) {
163+
compile: function(element, attrs) {
164+
return function(scope, element, attr, ctrl, $transclude) {
165165
ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
166-
ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: transclude, element: element });
166+
ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
167167
};
168168
}
169169
});
@@ -172,10 +172,8 @@ var ngSwitchDefaultDirective = ngDirective({
172172
transclude: 'element',
173173
priority: 800,
174174
require: '^ngSwitch',
175-
compile: function(element, attrs, transclude) {
176-
return function(scope, element, attr, ctrl) {
177-
ctrl.cases['?'] = (ctrl.cases['?'] || []);
178-
ctrl.cases['?'].push({ transclude: transclude, element: element });
179-
};
180-
}
175+
link: function(scope, element, attr, ctrl, $transclude) {
176+
ctrl.cases['?'] = (ctrl.cases['?'] || []);
177+
ctrl.cases['?'].push({ transclude: $transclude, element: element });
178+
}
181179
});

src/ngRoute/directive/ngView.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,7 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
173173
terminal: true,
174174
priority: 400,
175175
transclude: 'element',
176-
compile: function(element, attr, linker) {
177-
return function(scope, $element, attr) {
176+
link: function(scope, $element, attr, ctrl, $transclude) {
178177
var currentScope,
179178
currentElement,
180179
autoScrollExp = attr.autoscroll,
@@ -200,7 +199,7 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
200199

201200
if (template) {
202201
var newScope = scope.$new();
203-
linker(newScope, function(clone) {
202+
$transclude(newScope, function(clone) {
204203
clone.html(template);
205204
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
206205
if (angular.isDefined(autoScrollExp)
@@ -235,7 +234,6 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
235234
cleanupLastView();
236235
}
237236
}
238-
};
239237
}
240238
};
241239
}

test/ng/compileSpec.js

+201-2
Original file line numberDiff line numberDiff line change
@@ -3438,6 +3438,113 @@ describe('$compile', function() {
34383438
expect(log).toEqual('pre(); post(unicorn!)');
34393439
});
34403440
});
3441+
3442+
it('should copy the directive controller to all clones', function() {
3443+
var transcludeCtrl, cloneCount = 2;
3444+
module(function() {
3445+
directive('transclude', valueFn({
3446+
transclude: 'content',
3447+
controller: function($transclude) {
3448+
transcludeCtrl = this;
3449+
},
3450+
link: function(scope, el, attr, ctrl, $transclude) {
3451+
var i;
3452+
for (i=0; i<cloneCount; i++) {
3453+
$transclude(cloneAttach);
3454+
}
3455+
3456+
function cloneAttach(clone) {
3457+
el.append(clone);
3458+
}
3459+
}
3460+
}));
3461+
});
3462+
inject(function($compile) {
3463+
element = $compile('<div transclude><span></span></div>')($rootScope);
3464+
var children = element.children(), i;
3465+
expect(transcludeCtrl).toBeDefined();
3466+
3467+
expect(element.data('$transcludeController')).toBe(transcludeCtrl);
3468+
for (i=0; i<cloneCount; i++) {
3469+
expect(children.eq(i).data('$transcludeController')).toBeUndefined();
3470+
}
3471+
});
3472+
});
3473+
3474+
it('should provide the $transclude controller local as 5th argument to the pre and post-link function', function() {
3475+
var ctrlTransclude, preLinkTransclude, postLinkTransclude;
3476+
module(function() {
3477+
directive('transclude', valueFn({
3478+
transclude: 'content',
3479+
controller: function($transclude) {
3480+
ctrlTransclude = $transclude;
3481+
},
3482+
compile: function() {
3483+
return {
3484+
pre: function(scope, el, attr, ctrl, $transclude) {
3485+
preLinkTransclude = $transclude;
3486+
},
3487+
post: function(scope, el, attr, ctrl, $transclude) {
3488+
postLinkTransclude = $transclude;
3489+
}
3490+
};
3491+
}
3492+
}));
3493+
});
3494+
inject(function($compile) {
3495+
element = $compile('<div transclude></div>')($rootScope);
3496+
expect(ctrlTransclude).toBeDefined();
3497+
expect(ctrlTransclude).toBe(preLinkTransclude);
3498+
expect(ctrlTransclude).toBe(postLinkTransclude);
3499+
});
3500+
});
3501+
3502+
it('should allow an optional scope argument in $transclude', function() {
3503+
var capturedChildCtrl;
3504+
module(function() {
3505+
directive('transclude', valueFn({
3506+
transclude: 'content',
3507+
link: function(scope, element, attr, ctrl, $transclude) {
3508+
$transclude(scope, function(clone) {
3509+
element.append(clone);
3510+
});
3511+
}
3512+
}));
3513+
});
3514+
inject(function($compile) {
3515+
element = $compile('<div transclude>{{$id}}</div>')($rootScope);
3516+
$rootScope.$apply();
3517+
expect(element.text()).toBe($rootScope.$id);
3518+
});
3519+
3520+
});
3521+
3522+
it('should expose the directive controller to transcluded children', function() {
3523+
var capturedChildCtrl;
3524+
module(function() {
3525+
directive('transclude', valueFn({
3526+
transclude: 'content',
3527+
controller: function() {
3528+
},
3529+
link: function(scope, element, attr, ctrl, $transclude) {
3530+
$transclude(function(clone) {
3531+
element.append(clone);
3532+
});
3533+
}
3534+
}));
3535+
directive('child', valueFn({
3536+
require: '^transclude',
3537+
link: function(scope, element, attr, ctrl) {
3538+
capturedChildCtrl = ctrl;
3539+
}
3540+
}));
3541+
});
3542+
inject(function($compile) {
3543+
element = $compile('<div transclude><div child></div></div>')($rootScope);
3544+
expect(capturedChildCtrl).toBeTruthy();
3545+
});
3546+
3547+
});
34413548
});
34423549

34433550

@@ -3471,7 +3578,6 @@ describe('$compile', function() {
34713578
});
34723579
});
34733580

3474-
34753581
it('should only allow one element transclusion per element', function() {
34763582
module(function() {
34773583
directive('first', valueFn({
@@ -3620,8 +3726,101 @@ describe('$compile', function() {
36203726
]);
36213727
});
36223728
});
3623-
});
36243729

3730+
it('should allow to access $transclude in the same directive', function() {
3731+
var _$transclude;
3732+
module(function() {
3733+
directive('transclude', valueFn({
3734+
transclude: 'element',
3735+
controller: function($transclude) {
3736+
_$transclude = $transclude;
3737+
}
3738+
}));
3739+
});
3740+
inject(function($compile) {
3741+
element = $compile('<div transclude></div>')($rootScope);
3742+
expect(_$transclude).toBeDefined()
3743+
});
3744+
});
3745+
3746+
it('should copy the directive controller to all clones', function() {
3747+
var transcludeCtrl, cloneCount = 2;
3748+
module(function() {
3749+
directive('transclude', valueFn({
3750+
transclude: 'element',
3751+
controller: function() {
3752+
transcludeCtrl = this;
3753+
},
3754+
link: function(scope, el, attr, ctrl, $transclude) {
3755+
var i;
3756+
for (i=0; i<cloneCount; i++) {
3757+
$transclude(cloneAttach);
3758+
}
3759+
3760+
function cloneAttach(clone) {
3761+
el.after(clone);
3762+
}
3763+
}
3764+
}));
3765+
});
3766+
inject(function($compile) {
3767+
element = $compile('<div><div transclude></div></div>')($rootScope);
3768+
var children = element.children(), i;
3769+
for (i=0; i<cloneCount; i++) {
3770+
expect(children.eq(i).data('$transcludeController')).toBe(transcludeCtrl);
3771+
}
3772+
});
3773+
});
3774+
3775+
it('should expose the directive controller to transcluded children', function() {
3776+
var capturedTranscludeCtrl;
3777+
module(function() {
3778+
directive('transclude', valueFn({
3779+
transclude: 'element',
3780+
controller: function() {
3781+
},
3782+
link: function(scope, element, attr, ctrl, $transclude) {
3783+
$transclude(scope, function(clone) {
3784+
element.after(clone);
3785+
});
3786+
}
3787+
}));
3788+
directive('child', valueFn({
3789+
require: '^transclude',
3790+
link: function(scope, element, attr, ctrl) {
3791+
capturedTranscludeCtrl = ctrl;
3792+
}
3793+
}));
3794+
});
3795+
inject(function($compile) {
3796+
element = $compile('<div transclude><div child></div></div>')($rootScope);
3797+
expect(capturedTranscludeCtrl).toBeTruthy();
3798+
});
3799+
});
3800+
3801+
it('should allow access to $transclude in a templateUrl directive', function() {
3802+
var transclude;
3803+
module(function() {
3804+
directive('template', valueFn({
3805+
templateUrl: 'template.html',
3806+
replace: true
3807+
}));
3808+
directive('transclude', valueFn({
3809+
transclude: 'content',
3810+
controller: function($transclude) {
3811+
transclude = $transclude;
3812+
}
3813+
}));
3814+
});
3815+
inject(function($compile, $httpBackend) {
3816+
$httpBackend.expectGET('template.html').respond('<div transclude></div>');
3817+
element = $compile('<div template></div>')($rootScope);
3818+
$httpBackend.flush();
3819+
expect(transclude).toBeDefined();
3820+
});
3821+
});
3822+
3823+
});
36253824

36263825
it('should safely create transclude comment node and not break with "-->"',
36273826
inject(function($rootScope) {

test/ng/directive/ngIfSpec.js

+28
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,34 @@ describe('ngIf', function () {
148148

149149
});
150150

151+
describe('ngIf and transcludes', function() {
152+
it('should allow access to directive controller from children when used in a replace template', function() {
153+
var controller;
154+
module(function($compileProvider) {
155+
var directive = $compileProvider.directive;
156+
directive('template', valueFn({
157+
template: '<div ng-if="true"><span test></span></div>',
158+
replace: true,
159+
controller: function() {
160+
this.flag = true;
161+
}
162+
}));
163+
directive('test', valueFn({
164+
require: '^template',
165+
link: function(scope, el, attr, ctrl) {
166+
controller = ctrl;
167+
}
168+
}));
169+
});
170+
inject(function($compile, $rootScope) {
171+
var element = $compile('<div><div template></div></div>')($rootScope);
172+
$rootScope.$apply();
173+
expect(controller.flag).toBe(true);
174+
dealoc(element);
175+
});
176+
});
177+
});
178+
151179
describe('ngIf animations', function () {
152180
var body, element, $rootElement;
153181

0 commit comments

Comments
 (0)