diff --git a/Gruntfile.js b/Gruntfile.js index fb934d0b7850..2b68248f639a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -161,7 +161,7 @@ module.exports = function(grunt) { scenario: { dest: 'build/angular-scenario.js', src: [ - 'bower_components/jquery/jquery.js', + 'bower_components/jquery/dist/jquery.js', util.wrap([files['angularSrc'], files['angularScenario']], 'ngScenario/angular') ], styles: { diff --git a/angularFiles.js b/angularFiles.js index b1d5fd1c5527..6c6dc1e59af5 100755 --- a/angularFiles.js +++ b/angularFiles.js @@ -143,7 +143,7 @@ var angularFiles = { ], 'karma': [ - 'bower_components/jquery/jquery.js', + 'bower_components/jquery/dist/jquery.js', 'test/jquery_remove.js', '@angularSrc', 'src/publishExternalApis.js', @@ -177,7 +177,7 @@ var angularFiles = { ], 'karmaJquery': [ - 'bower_components/jquery/jquery.js', + 'bower_components/jquery/dist/jquery.js', 'test/jquery_alias.js', '@angularSrc', 'src/publishExternalApis.js', diff --git a/bower.json b/bower.json index a369d4174dad..a99f4bf3d71c 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "AngularJS", "devDependencies": { - "jquery": "1.10.2", + "jquery": "2.1.1", "lunr.js": "0.4.3", "open-sans-fontface": "1.0.4", "google-code-prettify": "1.0.1", diff --git a/docs/content/misc/faq.ngdoc b/docs/content/misc/faq.ngdoc index 3819296a7cc6..a3bcaa9f400b 100644 --- a/docs/content/misc/faq.ngdoc +++ b/docs/content/misc/faq.ngdoc @@ -75,14 +75,15 @@ The size of the file is < 36KB compressed and minified. Yes, you can use widgets from the [Closure Library](https://developers.google.com/closure/library/) in Angular. + ### Does Angular use the jQuery library? Yes, Angular can use [jQuery](http://jquery.com/) if it's present in your app when the application is being bootstrapped. If jQuery is not present in your script path, Angular falls back to its own implementation of the subset of jQuery that we call {@link angular.element jQLite}. -Due to a change to use `on()`/`off()` rather than `bind()`/`unbind()`, Angular 1.2 only operates with -jQuery 1.7.1 or above. However, Angular does not currently support jQuery 2.x or above. +Angular 1.3 only supports jQuery 2.1 or above. jQuery 1.7 and newer might work correctly with Angular +but we don't guarantee that. ### What is testability like in Angular? diff --git a/docs/content/tutorial/step_12.ngdoc b/docs/content/tutorial/step_12.ngdoc index 0d1646a43a48..7e8c20e3f184 100644 --- a/docs/content/tutorial/step_12.ngdoc +++ b/docs/content/tutorial/step_12.ngdoc @@ -104,7 +104,8 @@ __`app/index.html`.__ ```
- **Important:** Be sure to use jQuery version `1.10.x`. AngularJS does not yet support jQuery `2.x`. + **Important:** Be sure to use jQuery version 2.1 or newer when using Angular 1.3; jQuery 1.x is + not officially supported. Be sure to load jQuery before all AngularJS scripts, otherwise AngularJS won't detect jQuery and animations will not work as expected.
diff --git a/src/Angular.js b/src/Angular.js index 1caab1545dc0..d1d4d70f3b31 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -1459,7 +1459,9 @@ function bindJQuery() { // bind to jQuery if present; jQuery = window.jQuery; // Use jQuery if it exists with proper functionality, otherwise default to us. - // Angular 1.2+ requires jQuery 1.7.1+ for on()/off() support. + // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. + // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older + // versions. It will not work for sure with jQuery <1.7, though. if (jQuery && jQuery.fn.on) { jqLite = jQuery; extend(jQuery.fn, { diff --git a/src/ng/compile.js b/src/ng/compile.js index 5a8103d7f024..a68fc2f091b9 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -2031,7 +2031,25 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } var fragment = document.createDocumentFragment(); fragment.appendChild(firstElementToRemove); - newNode[jqLite.expando] = firstElementToRemove[jqLite.expando]; + + // Copy over user data (that includes Angular's $scope etc.). Don't copy private + // data here because there's no public interface in jQuery to do that and copying over + // event listeners (which is the main use of private data) wouldn't work anyway. + jqLite(newNode).data(jqLite(firstElementToRemove).data()); + + // Remove data of the replaced element. We cannot just call .remove() + // on the element it since that would deallocate scope and event handlers that are still needed + // for the new node. Instead, remove the data "manually". + if (!jQuery) { + delete jqLite.cache[firstElementToRemove[jqLite.expando]]; + } else { + // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after the replaced + // element. Note that we need to use the original method here and not the one monkey-patched by Angular + // since the patched method emits the $destroy event causing the scope to be trashed and we do need + // the very same scope to work with the new element. + jQuery.cleanData.$$original([firstElementToRemove]); + } + for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { var element = elementsToRemove[k]; jqLite(element).remove(); // must do this way to clean up expando diff --git a/src/ngMock/.jshintrc b/src/ngMock/.jshintrc index 415ab1bea7a7..6435e7501d38 100644 --- a/src/ngMock/.jshintrc +++ b/src/ngMock/.jshintrc @@ -3,6 +3,7 @@ "browser": true, "globals": { "angular": false, - "expect": false + "expect": false, + "jQuery": false } } \ No newline at end of file diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index 3b03db47d991..2dd4fef5ea3f 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -1956,6 +1956,13 @@ angular.mock.e2e.$httpBackendDecorator = angular.mock.clearDataCache = function() { + // jQuery 2.x doesn't expose data attached to elements. We could use jQuery.cleanData + // to clean up after elements but we'd first need to know which elements to clean up after. + // Skip it then. + if (window.jQuery) { + return; + } + var key, cache = angular.element.cache; diff --git a/test/helpers/testabilityPatch.js b/test/helpers/testabilityPatch.js index 11d5d5389ff1..723eb6449d33 100644 --- a/test/helpers/testabilityPatch.js +++ b/test/helpers/testabilityPatch.js @@ -34,6 +34,8 @@ beforeEach(function() { }); afterEach(function() { + var count, cache; + if (this.$injector) { var $rootScope = this.$injector.get('$rootScope'); var $rootElement = this.$injector.get('$rootElement'); @@ -46,25 +48,27 @@ afterEach(function() { $log.assertEmpty && $log.assertEmpty(); } - // complain about uncleared jqCache references - var count = 0; + if (!window.jQuery) { + // jQuery 2.x doesn't expose the cache storage. - // This line should be enabled as soon as this bug is fixed: http://bugs.jquery.com/ticket/11775 - //var cache = jqLite.cache; - var cache = angular.element.cache; + // complain about uncleared jqCache references + count = 0; - forEachSorted(cache, function(expando, key){ - angular.forEach(expando.data, function(value, key){ - count ++; - if (value && value.$element) { - dump('LEAK', key, value.$id, sortedHtml(value.$element)); - } else { - dump('LEAK', key, angular.toJson(value)); - } + cache = angular.element.cache; + + forEachSorted(cache, function (expando, key) { + angular.forEach(expando.data, function (value, key) { + count++; + if (value && value.$element) { + dump('LEAK', key, value.$id, sortedHtml(value.$element)); + } else { + dump('LEAK', key, angular.toJson(value)); + } + }); }); - }); - if (count) { - throw new Error('Found jqCache references that were not deallocated! count: ' + count); + if (count) { + throw new Error('Found jqCache references that were not deallocated! count: ' + count); + } } @@ -95,8 +99,9 @@ function dealoc(obj) { if (obj) { if (angular.isElement(obj)) { cleanup(angular.element(obj)); - } else { - for(var key in jqCache) { + } else if (!window.jQuery) { + // jQuery 2.x doesn't expose the cache storage. + for (var key in jqCache) { var value = jqCache[key]; if (value.data && value.data.$scope == obj) { delete jqCache[key]; diff --git a/test/jqLiteSpec.js b/test/jqLiteSpec.js index 1eb8d319b246..38ec858f03e7 100644 --- a/test/jqLiteSpec.js +++ b/test/jqLiteSpec.js @@ -130,14 +130,6 @@ describe('jqLite', function() { }); describe('_data', function() { - it('should provide access to the data present on the element', function() { - var element = jqLite('foo'); - var data = ['value']; - element.data('val', data); - expect(angular.element._data(element[0]).data.val).toBe(data); - dealoc(element); - }); - it('should provide access to the events present on the element', function() { var element = jqLite('foo'); expect(angular.element._data(element[0]).events).toBeUndefined(); diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index d2f1aec74b5a..27661dfa0e45 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -4028,7 +4028,12 @@ describe('$compile', function() { - it('should not leak if two "element" transclusions are on the same element', function() { + it('should not leak if two "element" transclusions are on the same element', function () { + if (jQuery) { + // jQuery 2.x doesn't expose the cache storage. + return; + } + var calcCacheSize = function() { var size = 0; forEach(jqLite.cache, function(item, key) { size++; }); @@ -4056,7 +4061,11 @@ describe('$compile', function() { }); - it('should not leak if two "element" transclusions are on the same element', function() { + it('should not leak if two "element" transclusions are on the same element', function () { + if (jQuery) { + // jQuery 2.x doesn't expose the cache storage. + return; + } var calcCacheSize = function() { var size = 0; forEach(jqLite.cache, function(item, key) { size++; }); @@ -4086,6 +4095,29 @@ describe('$compile', function() { }); }); + if (jQuery) { + it('should clean up after a replaced element', inject(function ($compile) { + var privateData, firstRepeatedElem; + + element = $compile('
{{x}}
')($rootScope); + + $rootScope.$apply('xs = [0,1]'); + firstRepeatedElem = element.children('.ng-scope').eq(0); + + expect(firstRepeatedElem.data('$scope')).toBeDefined(); + privateData = jQuery._data(firstRepeatedElem[0]); + expect(privateData.events).toBeDefined(); + expect(privateData.events.$destroy).toBeDefined(); + expect(privateData.events.$destroy[0]).toBeDefined(); + + $rootScope.$apply('xs = null'); + + expect(firstRepeatedElem.data('$scope')).not.toBeDefined(); + privateData = jQuery._data(firstRepeatedElem[0]); + expect(privateData && privateData.events).not.toBeDefined(); + })); + } + it('should remove transclusion scope, when the DOM is destroyed', function() { module(function() { diff --git a/test/ngMock/angular-mocksSpec.js b/test/ngMock/angular-mocksSpec.js index 9934c37db6a2..35b394e47aac 100644 --- a/test/ngMock/angular-mocksSpec.js +++ b/test/ngMock/angular-mocksSpec.js @@ -706,6 +706,11 @@ describe('ngMock', function() { describe('angular.mock.clearDataCache', function() { + if (window.jQuery) { + // jQuery 2.x doesn't expose the cache storage. + return; + } + function keys(obj) { var keysArr = []; for(var key in obj) {