Skip to content

Commit cced1ae

Browse files
committed
fix(ngSwitch): not leak when transcluded and cloned
The leak can occur when ngSwich is used inside ngRepeat or any other directive which does not attached transcluded content to DOM but clones it. Refactor ngSwitch to use controller instead of storing data on compile node. Closes angular#1621
1 parent 7dff7bb commit cced1ae

File tree

2 files changed

+45
-32
lines changed

2 files changed

+45
-32
lines changed

src/ng/directive/ngSwitch.js

+33-32
Original file line numberDiff line numberDiff line change
@@ -62,51 +62,52 @@
6262
var NG_SWITCH = 'ng-switch';
6363
var ngSwitchDirective = valueFn({
6464
restrict: 'EA',
65-
compile: function(element, attr) {
65+
require: 'ngSwitch',
66+
controller: function ngSwitchController() {
67+
this.cases = {};
68+
},
69+
link: function(scope, element, attr, ctrl) {
6670
var watchExpr = attr.ngSwitch || attr.on,
67-
cases = {};
71+
selectedTransclude,
72+
selectedElement,
73+
selectedScope;
6874

69-
element.data(NG_SWITCH, cases);
70-
return function(scope, element){
71-
var selectedTransclude,
72-
selectedElement,
73-
selectedScope;
74-
75-
scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
76-
if (selectedElement) {
77-
selectedScope.$destroy();
78-
selectedElement.remove();
79-
selectedElement = selectedScope = null;
80-
}
81-
if ((selectedTransclude = cases['!' + value] || cases['?'])) {
82-
scope.$eval(attr.change);
83-
selectedScope = scope.$new();
84-
selectedTransclude(selectedScope, function(caseElement) {
85-
selectedElement = caseElement;
86-
element.append(caseElement);
87-
});
88-
}
89-
});
90-
};
75+
scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
76+
if (selectedElement) {
77+
selectedScope.$destroy();
78+
selectedElement.remove();
79+
selectedElement = selectedScope = null;
80+
}
81+
if ((selectedTransclude = ctrl.cases['!' + value] || ctrl.cases['?'])) {
82+
scope.$eval(attr.change);
83+
selectedScope = scope.$new();
84+
selectedTransclude(selectedScope, function(caseElement) {
85+
selectedElement = caseElement;
86+
element.append(caseElement);
87+
});
88+
}
89+
});
9190
}
9291
});
9392

9493
var ngSwitchWhenDirective = ngDirective({
9594
transclude: 'element',
9695
priority: 500,
97-
compile: function(element, attrs, transclude) {
98-
var cases = element.inheritedData(NG_SWITCH);
99-
assertArg(cases);
100-
cases['!' + attrs.ngSwitchWhen] = transclude;
96+
require: '^ngSwitch',
97+
compile: function(element, attrs, transclude, ctrl) {
98+
return function(scope, element, attr, ctrl) {
99+
ctrl.cases['!' + attrs.ngSwitchWhen] = transclude;
100+
};
101101
}
102102
});
103103

104104
var ngSwitchDefaultDirective = ngDirective({
105105
transclude: 'element',
106106
priority: 500,
107-
compile: function(element, attrs, transclude) {
108-
var cases = element.inheritedData(NG_SWITCH);
109-
assertArg(cases);
110-
cases['?'] = transclude;
107+
require: '^ngSwitch',
108+
compile: function(element, attrs, transclude, ctrl) {
109+
return function(scope, element, attr, ctrl) {
110+
ctrl.cases['?'] = transclude;
111+
};
111112
}
112113
});

test/ng/directive/ngSwitchSpec.js

+12
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,16 @@ describe('ngSwitch', function() {
9090
expect(child2).toBeDefined();
9191
expect(child2).not.toBe(child1);
9292
}));
93+
94+
95+
it('should not leak when comiled node is not attached to DOM but cloned',
96+
inject(function($rootScope, $compile) {
97+
element = $compile(
98+
'<div ng-repeat="i in []">' +
99+
'<ng-switch on="url">' +
100+
'<div ng-switch-when="a">{{name}}</div>' +
101+
'</ng-switch>' +
102+
'</div>')($rootScope);
103+
$rootScope.$apply();
104+
}));
93105
});

0 commit comments

Comments
 (0)