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

Commit c656d43

Browse files
committed
refactor($compile): remove skipDestroyOnNextJQueryCleanData, remove jq data of all replaced nodes
1 parent 9762acb commit c656d43

File tree

5 files changed

+46
-50
lines changed

5 files changed

+46
-50
lines changed

src/.jshintrc

-2
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,6 @@
9494
"VALIDITY_STATE_PROPERTY": false,
9595
"reloadWithDebugInfo": false,
9696

97-
"skipDestroyOnNextJQueryCleanData": true,
98-
9997
"NODE_TYPE_ELEMENT": false,
10098
"NODE_TYPE_ATTRIBUTE": false,
10199
"NODE_TYPE_TEXT": false,

src/Angular.js

+4-9
Original file line numberDiff line numberDiff line change
@@ -1653,7 +1653,6 @@ function snake_case(name, separator) {
16531653
}
16541654

16551655
var bindJQueryFired = false;
1656-
var skipDestroyOnNextJQueryCleanData;
16571656
function bindJQuery() {
16581657
var originalCleanData;
16591658

@@ -1688,15 +1687,11 @@ function bindJQuery() {
16881687
originalCleanData = jQuery.cleanData;
16891688
jQuery.cleanData = function(elems) {
16901689
var events;
1691-
if (!skipDestroyOnNextJQueryCleanData) {
1692-
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
1693-
events = jQuery._data(elem, "events");
1694-
if (events && events.$destroy) {
1695-
jQuery(elem).triggerHandler('$destroy');
1696-
}
1690+
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
1691+
events = jQuery._data(elem, "events");
1692+
if (events && events.$destroy) {
1693+
jQuery(elem).triggerHandler('$destroy');
16971694
}
1698-
} else {
1699-
skipDestroyOnNextJQueryCleanData = false;
17001695
}
17011696
originalCleanData(elems);
17021697
};

src/jqLite.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,12 @@ function jqLiteHasData(node) {
189189
return false;
190190
}
191191

192+
function jqLiteCleanData(nodes) {
193+
for (var i = 0, ii = nodes.length; i < ii; i++) {
194+
jqLiteRemoveData(nodes[i]);
195+
}
196+
}
197+
192198
function jqLiteBuildFragment(html, context) {
193199
var tmp, tag, wrap,
194200
fragment = context.createDocumentFragment(),
@@ -564,7 +570,8 @@ function getAliasedAttrName(element, name) {
564570
forEach({
565571
data: jqLiteData,
566572
removeData: jqLiteRemoveData,
567-
hasData: jqLiteHasData
573+
hasData: jqLiteHasData,
574+
cleanData: jqLiteCleanData
568575
}, function(fn, name) {
569576
JQLite[name] = fn;
570577
});

src/ng/compile.js

+17-24
Original file line numberDiff line numberDiff line change
@@ -2501,41 +2501,34 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
25012501
parent.replaceChild(newNode, firstElementToRemove);
25022502
}
25032503

2504-
// TODO(perf): what's this document fragment for? is it needed? can we at least reuse it?
2504+
// Append all the `elementsToRemove` to a fragment. This will...
2505+
// - remove them from the DOM
2506+
// - allow them to still be traversed with .nextSibling
2507+
// - allow a single fragment.qSA to fetch all elements being removed
25052508
var fragment = document.createDocumentFragment();
2506-
fragment.appendChild(firstElementToRemove);
2509+
for (i = 0; i < removeCount; i++) {
2510+
fragment.appendChild(elementsToRemove[i]);
2511+
}
25072512

25082513
if (jqLite.hasData(firstElementToRemove)) {
25092514
// Copy over user data (that includes Angular's $scope etc.). Don't copy private
25102515
// data here because there's no public interface in jQuery to do that and copying over
25112516
// event listeners (which is the main use of private data) wouldn't work anyway.
25122517
jqLite(newNode).data(jqLite(firstElementToRemove).data());
25132518

2514-
// Remove data of the replaced element. We cannot just call .remove()
2515-
// on the element it since that would deallocate scope that is needed
2516-
// for the new node. Instead, remove the data "manually".
2517-
if (!jQuery) {
2518-
delete jqLite.cache[firstElementToRemove[jqLite.expando]];
2519-
} else {
2520-
// jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
2521-
// the replaced element. The cleanData version monkey-patched by Angular would cause
2522-
// the scope to be trashed and we do need the very same scope to work with the new
2523-
// element. However, we cannot just cache the non-patched version and use it here as
2524-
// that would break if another library patches the method after Angular does (one
2525-
// example is jQuery UI). Instead, set a flag indicating scope destroying should be
2526-
// skipped this one time.
2527-
skipDestroyOnNextJQueryCleanData = true;
2528-
jQuery.cleanData([firstElementToRemove]);
2529-
}
2519+
// Remove $destroy event listeners from `firstElementToRemove` since it is being
2520+
// replaced, not destroyed.
2521+
jqLite(firstElementToRemove).off('$destroy');
25302522
}
25312523

2532-
for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
2533-
var element = elementsToRemove[k];
2534-
jqLite(element).remove(); // must do this way to clean up expando
2535-
fragment.appendChild(element);
2536-
delete elementsToRemove[k];
2537-
}
2524+
// Cleanup any data/listeners on the elements and children.
2525+
// This includes invoking the $destroy event on any elements with listeners.
2526+
jqLite.cleanData(fragment.querySelectorAll('*'));
25382527

2528+
// Update the jqLite collection to only contain the `newNode`
2529+
for (i = 1; i < removeCount; i++) {
2530+
delete elementsToRemove[i];
2531+
}
25392532
elementsToRemove[0] = newNode;
25402533
elementsToRemove.length = 1;
25412534
}

test/ng/compileSpec.js

+17-14
Original file line numberDiff line numberDiff line change
@@ -5676,7 +5676,9 @@ describe('$compile', function() {
56765676

56775677
testCleanup();
56785678

5679-
expect(cleanedCount).toBe(xs.length);
5679+
// The ng-repeat template is removed/cleaned (the +1)
5680+
// and each clone of the ng-repeat template is also removed (xs.length)
5681+
expect(cleanedCount).toBe(xs.length + 1);
56805682

56815683
// Restore the previous jQuery.cleanData.
56825684
jQuery.cleanData = currentCleanData;
@@ -7889,7 +7891,7 @@ describe('$compile', function() {
78897891

78907892
$compileProvider.directive('foo', function() {
78917893
return {
7892-
priority: 1, //before the replace directive
7894+
priority: 1, // before the replace directive
78937895
link: function($scope, $element, $attrs) {
78947896
linkCalls.push($attrs.foo);
78957897
$element.on('$destroy', function() {
@@ -7901,7 +7903,7 @@ describe('$compile', function() {
79017903
});
79027904

79037905
inject(function($compile, $templateCache, $rootScope) {
7904-
$templateCache.put('template123', '<div></div>');
7906+
$templateCache.put('template123', '<p></p>');
79057907

79067908
$compile(
79077909
'<div replace-start foo="1"><span foo="1.1"></span></div>' +
@@ -7918,6 +7920,7 @@ describe('$compile', function() {
79187920
});
79197921

79207922
function getAll($root) {
7923+
// check for .querySelectorAll to support comment nodes
79217924
return [$root[0]].concat($root[0].querySelectorAll ? sliceArgs($root[0].querySelectorAll('*')) : []);
79227925
}
79237926

@@ -7949,16 +7952,16 @@ describe('$compile', function() {
79497952
testCompileLinkDataCleanup('<div ng-if-start="false"><span><span/></div><span></span><div ng-if-end><span></span></div>');
79507953
});
79517954

7952-
function testReplaceElementCleanup(replace, transclude, asyncTemplate) {
7955+
function testReplaceElementCleanup(dirOptions) {
79537956
var template = '<div></div>';
79547957
module(function($compileProvider) {
79557958
$compileProvider.directive('theDir', function() {
79567959
return {
79577960
multiElement: true,
7958-
replace: replace,
7959-
transclude: transclude,
7960-
template: asyncTemplate ? undefined : template,
7961-
templateUrl: asyncTemplate ? 'the-dir-template-url' : undefined
7961+
replace: dirOptions.replace,
7962+
transclude: dirOptions.transclude,
7963+
template: dirOptions.asyncTemplate ? undefined : template,
7964+
templateUrl: dirOptions.asyncTemplate ? 'the-dir-template-url' : undefined
79627965
};
79637966
});
79647967
});
@@ -7975,22 +7978,22 @@ describe('$compile', function() {
79757978
});
79767979
}
79777980
it('should clean data of elements removed for directive template', function() {
7978-
testReplaceElementCleanup(false, false, false);
7981+
testReplaceElementCleanup({});
79797982
});
79807983
it('should clean data of elements removed for directive templateUrl', function() {
7981-
testReplaceElementCleanup(false, false, true);
7984+
testReplaceElementCleanup({asyncTmeplate: true});
79827985
});
79837986
it('should clean data of elements transcluded into directive template', function() {
7984-
testReplaceElementCleanup(false, true, false);
7987+
testReplaceElementCleanup({transclude: true});
79857988
});
79867989
it('should clean data of elements transcluded into directive templateUrl', function() {
7987-
testReplaceElementCleanup(false, true, true);
7990+
testReplaceElementCleanup({transclude: true, asyncTmeplate: true});
79887991
});
79897992
it('should clean data of elements replaced with directive template', function() {
7990-
testReplaceElementCleanup(true, false, false);
7993+
testReplaceElementCleanup({replace: true});
79917994
});
79927995
it('should clean data of elements replaced with directive templateUrl', function() {
7993-
testReplaceElementCleanup(true, false, true);
7996+
testReplaceElementCleanup({replace: true, asyncTemplate: true});
79947997
});
79957998
});
79967999
});

0 commit comments

Comments
 (0)