diff --git a/src/ng/http.js b/src/ng/http.js
index efae76332372..a0715d494fd7 100644
--- a/src/ng/http.js
+++ b/src/ng/http.js
@@ -343,34 +343,6 @@ function $HttpProvider() {
jsonpCallbackParam: 'callback'
};
- var useApplyAsync = false;
- /**
- * @ngdoc method
- * @name $httpProvider#useApplyAsync
- * @description
- *
- * Configure $http service to combine processing of multiple http responses received at around
- * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
- * significant performance improvement for bigger applications that make many HTTP requests
- * concurrently (common during application bootstrap).
- *
- * Defaults to false. If no value is specified, returns the current configured value.
- *
- * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
- * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
- * to load and share the same digest cycle.
- *
- * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
- * otherwise, returns the current configured value.
- **/
- this.useApplyAsync = function(value) {
- if (isDefined(value)) {
- useApplyAsync = !!value;
- return this;
- }
- return useApplyAsync;
- };
-
/**
* @ngdoc property
* @name $httpProvider#interceptors
@@ -1342,17 +1314,9 @@ function $HttpProvider() {
var applyHandlers = {};
forEach(eventHandlers, function(eventHandler, key) {
applyHandlers[key] = function(event) {
- if (useApplyAsync) {
- $rootScope.$applyAsync(callEventHandler);
- } else if ($rootScope.$$phase) {
- callEventHandler();
- } else {
- $rootScope.$apply(callEventHandler);
- }
-
- function callEventHandler() {
+ $rootScope.$evalAsync(function callEventHandler() {
eventHandler(event);
- }
+ });
};
});
return applyHandlers;
@@ -1364,7 +1328,6 @@ function $HttpProvider() {
* Callback registered to $httpBackend():
* - caches the response if desired
* - resolves the raw $http promise
- * - calls $apply
*/
function done(status, response, headersString, statusText, xhrStatus) {
if (cache) {
@@ -1376,16 +1339,7 @@ function $HttpProvider() {
}
}
- function resolveHttpPromise() {
- resolvePromise(response, status, headersString, statusText, xhrStatus);
- }
-
- if (useApplyAsync) {
- $rootScope.$applyAsync(resolveHttpPromise);
- } else {
- resolveHttpPromise();
- if (!$rootScope.$$phase) $rootScope.$apply();
- }
+ resolvePromise(response, status, headersString, statusText, xhrStatus);
}
diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js
index 8ee32d27a955..6cdac928ab59 100644
--- a/src/ngMock/angular-mocks.js
+++ b/src/ngMock/angular-mocks.js
@@ -1849,10 +1849,14 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
var part = responses.splice(skip, 1);
if (!part.length) throw new Error('No more pending request to flush !');
part[0]();
+ // Calling $digest again to execute the callbacks of the promises returned from $http
+ // as these callbacks can add new requests to the queue to flush.
+ if (digest !== false) $rootScope.$digest();
}
} else {
while (responses.length > skip) {
responses.splice(skip, 1)[0]();
+ if (digest !== false) $rootScope.$digest();
}
}
$httpBackend.verifyNoOutstandingExpectation(digest);
diff --git a/test/ng/directive/ngIncludeSpec.js b/test/ng/directive/ngIncludeSpec.js
index e1261c2040e7..26631426c55c 100644
--- a/test/ng/directive/ngIncludeSpec.js
+++ b/test/ng/directive/ngIncludeSpec.js
@@ -362,18 +362,23 @@ describe('ngInclude', function() {
it('should construct SVG template elements with correct namespace', function() {
if (!window.SVGRectElement) return;
- module(function($compileProvider) {
+ module(function($compileProvider, $provide) {
$compileProvider.directive('test', valueFn({
templateNamespace: 'svg',
templateUrl: 'my-rect.html',
replace: true
}));
+ $provide.decorator('$templateRequest', function($delegate) {
+ return jasmine.createSpy('$templateRequest').and.callFake($delegate);
+ });
});
- inject(function($compile, $rootScope, $httpBackend) {
+ inject(function($compile, $rootScope, $httpBackend, $templateRequest) {
$httpBackend.expectGET('my-rect.html').respond('');
$httpBackend.expectGET('include.svg').respond('');
element = $compile('')($rootScope);
- $httpBackend.flush();
+ expect($templateRequest).toHaveBeenCalledTimes(1);
+ $httpBackend.flush(2);
+ expect($templateRequest).toHaveBeenCalledTimes(2);
var child = element.find('rect');
expect(child.length).toBe(2);
// eslint-disable-next-line no-undef
diff --git a/test/ng/httpSpec.js b/test/ng/httpSpec.js
index aad004790842..b924e6fa0c2d 100644
--- a/test/ng/httpSpec.js
+++ b/test/ng/httpSpec.js
@@ -301,7 +301,6 @@ describe('$http', function() {
$http = $h;
$rootScope = $rs;
$sce = $sc;
- spyOn($rootScope, '$apply').and.callThrough();
}]));
it('should throw error if the request configuration is not an object', function() {
@@ -1073,35 +1072,7 @@ describe('$http', function() {
describe('callbacks', function() {
- it('should $apply after success callback', function() {
- $httpBackend.when('GET').respond(200);
- $http({method: 'GET', url: '/some'});
- $httpBackend.flush();
- expect($rootScope.$apply).toHaveBeenCalledOnce();
- });
-
-
- it('should $apply after error callback', function() {
- $httpBackend.when('GET').respond(404);
- $http({method: 'GET', url: '/some'}).catch(noop);
- $httpBackend.flush();
- expect($rootScope.$apply).toHaveBeenCalledOnce();
- });
-
-
- it('should $apply even if exception thrown during callback', inject(function($exceptionHandler) {
- $httpBackend.when('GET').respond(200);
- callback.and.throwError('error in callback');
-
- $http({method: 'GET', url: '/some'}).then(callback);
- $httpBackend.flush();
- expect($rootScope.$apply).toHaveBeenCalledOnce();
-
- $exceptionHandler.errors = [];
- }));
-
-
- it('should pass the event handlers through to the backend', function() {
+ it('should pass the event handlers through to the backend', inject(function($browser) {
var progressFn = jasmine.createSpy('progressFn');
var uploadProgressFn = jasmine.createSpy('uploadProgressFn');
$httpBackend.when('GET').respond(200);
@@ -1117,16 +1088,17 @@ describe('$http', function() {
expect(mockXHR.upload.$$events.progress).toEqual(jasmine.any(Function));
var eventObj = {};
- spyOn($rootScope, '$digest');
mockXHR.$$events.progress(eventObj);
+ // The invocation of the callback is scheduled via `$evalAsync`,
+ // that's why `flush` is needed here.
+ $browser.defer.flush();
expect(progressFn).toHaveBeenCalledOnceWith(eventObj);
- expect($rootScope.$digest).toHaveBeenCalledTimes(1);
mockXHR.upload.$$events.progress(eventObj);
+ $browser.defer.flush();
expect(uploadProgressFn).toHaveBeenCalledOnceWith(eventObj);
- expect($rootScope.$digest).toHaveBeenCalledTimes(2);
- });
+ }));
});
@@ -2293,35 +2265,27 @@ describe('$http', function() {
});
-describe('$http with $applyAsync', function() {
- var $http, $httpBackend, $rootScope, $browser, log;
- beforeEach(module(function($httpProvider) {
- $httpProvider.useApplyAsync(true);
- }, provideLog));
-
+describe('$http interacting with digest cycle', function() {
+ var $http, $httpBackend, $browser, log;
+ beforeEach(module(provideLog));
- beforeEach(inject(['$http', '$httpBackend', '$rootScope', '$browser', 'log', function(http, backend, scope, browser, logger) {
+ beforeEach(inject(['$http', '$httpBackend', '$browser', 'log', function(http, backend, browser, logger) {
$http = http;
$httpBackend = backend;
- $rootScope = scope;
$browser = browser;
- spyOn($rootScope, '$apply').and.callThrough();
- spyOn($rootScope, '$applyAsync').and.callThrough();
- spyOn($rootScope, '$digest').and.callThrough();
- spyOn($browser.defer, 'cancel').and.callThrough();
log = logger;
}]));
- it('should schedule coalesced apply on response', function() {
+ it('should execute callbacks asynchronously in $digest', function() {
var handler = jasmine.createSpy('handler');
$httpBackend.expect('GET', '/template1.html').respond(200, '
Header!
', {});
$http.get('/template1.html').then(handler);
- // Ensure requests are sent
- $rootScope.$digest();
+ // Ensure requests are sent. ($http is internally promise-based and doesn't start working until
+ // $digest occurs.)
+ $browser.defer.flush();
$httpBackend.flush(null, null, false);
- expect($rootScope.$applyAsync).toHaveBeenCalledOnce();
expect(handler).not.toHaveBeenCalled();
$browser.defer.flush();
@@ -2336,11 +2300,14 @@ describe('$http with $applyAsync', function() {
$http.get('/template1.html').then(log.fn('response 1'));
$http.get('/template2.html').then(log.fn('response 2'));
// Ensure requests are sent
- $rootScope.$digest();
+ $browser.defer.flush();
+ // Resolve the promises. When a $q promise is resolved it uses $rootScope.$evalAsync to schedule
+ // the execution of its callbacks.
$httpBackend.flush(null, null, false);
expect(log).toEqual([]);
+ // Execute the promises' callbacks in the $digest scheduled with $evalAsync
$browser.defer.flush();
expect(log).toEqual(['response 1', 'response 2']);
});
@@ -2354,16 +2321,16 @@ describe('$http with $applyAsync', function() {
$http.get('/template1.html').then(log.fn('response 1'));
$http.get('/template2.html').then(log.fn('response 2'));
$http.get('/template3.html').then(log.fn('response 3'));
- // Ensure requests are sent
- $rootScope.$digest();
// Intermediate $digest occurs before 3rd response is received, assert that pending responses
- /// are handled
+ // are handled. Unless false is passed as the second parameter, $httpBackend.flush calls
+ // $rootScope.$digest at least twice (before and after doing the flush).
$httpBackend.flush(2);
expect(log).toEqual(['response 1', 'response 2']);
- // Finally, third response is received, and a second coalesced $apply is started
+ // Finally, third response is received, and its callback is scheduled with $evalAsync
$httpBackend.flush(null, null, false);
+ // Execute the promises' callbacks in the $digest scheduled with $evalAsync
$browser.defer.flush();
expect(log).toEqual(['response 1', 'response 2', 'response 3']);
});