diff --git a/src/ng/http.js b/src/ng/http.js index 295ba68263e8..dc097a6f613e 100644 --- a/src/ng/http.js +++ b/src/ng/http.js @@ -801,8 +801,12 @@ function $HttpProvider() { * - **headers** – `{Object}` – Map of strings or functions which return strings representing * HTTP headers to send to the server. If the return value of a function is null, the * header will not be sent. Functions accept a config object as an argument. - * - **events** - `{Object}` - Event listeners to be bound to the XMLHttpRequest object. - * To bind events to the XMLHttpRequest upload object, nest it under the upload key. + * - **eventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest object. + * To bind events to the XMLHttpRequest upload object, use `uploadEventHandlers`. + * The handler will be called in the context of a `$apply` block. + * - **uploadEventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest upload + * object. To bind events to the XMLHttpRequest object, use `eventHandlers`. + * The handler will be called in the context of a `$apply` block. * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token. * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token. * - **transformRequest** – @@ -1261,11 +1265,31 @@ function $HttpProvider() { } $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, - config.withCredentials, config.responseType, config.events); + config.withCredentials, config.responseType, + createApplyHandlers(config.eventHandlers), + createApplyHandlers(config.uploadEventHandlers)); } return promise; + function createApplyHandlers(eventHandlers) { + if (eventHandlers) { + var applyHandlers = {}; + forEach(eventHandlers, function(eventHandler, key) { + applyHandlers[key] = function() { + if (useApplyAsync) { + $rootScope.$applyAsync(eventHandler); + } else if ($rootScope.$$phase) { + eventHandler(); + } else { + $rootScope.$apply(eventHandler); + } + }; + }); + return applyHandlers; + } + } + /** * Callback registered to $httpBackend(): diff --git a/src/ng/httpBackend.js b/src/ng/httpBackend.js index b961874ee329..add2af6e7c0d 100644 --- a/src/ng/httpBackend.js +++ b/src/ng/httpBackend.js @@ -54,7 +54,7 @@ function $HttpBackendProvider() { function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { // TODO(vojta): fix the signature - return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers) { + return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) { $browser.$$incOutstandingRequestCount(); url = url || $browser.url(); @@ -114,19 +114,13 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc xhr.onerror = requestError; xhr.onabort = requestError; - if (eventHandlers) { - forEach(eventHandlers, function(value, key) { - if (key !== 'upload') { - xhr.addEventListener(key, value); - } - }); + forEach(eventHandlers, function(value, key) { + xhr.addEventListener(key, value); + }); - if (eventHandlers.upload) { - forEach(eventHandlers.upload, function(value, key) { - xhr.upload.addEventListener(key, value); - }); - } - } + forEach(uploadEventHandlers, function(value, key) { + xhr.upload.addEventListener(key, value); + }); if (withCredentials) { xhr.withCredentials = true; diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index b068d0d32079..11962e2c3f21 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -1321,12 +1321,15 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { } // TODO(vojta): change params to: method, url, data, headers, callback - function $httpBackend(method, url, data, callback, headers, timeout, withCredentials, responseType) { + function $httpBackend(method, url, data, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) { var xhr = new MockXhr(), expectation = expectations[0], wasExpected = false; + xhr.$$events = eventHandlers; + xhr.upload.$$events = uploadEventHandlers; + function prettyPrint(data) { return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) ? data @@ -2010,6 +2013,9 @@ function MockXhr() { this.abort = angular.noop; + // This section simulates the events on a real XHR object (and the upload object) + // When we are testing $httpBackend (inside the angular project) we make partial use of this + // but store the events directly ourselves on `$$events`, instead of going through the `addEventListener` this.$$events = {}; this.addEventListener = function(name, listener) { if (angular.isUndefined(this.$$events[name])) this.$$events[name] = []; diff --git a/test/ng/httpBackendSpec.js b/test/ng/httpBackendSpec.js index a0ec76dd8e19..af3175acfd6d 100644 --- a/test/ng/httpBackendSpec.js +++ b/test/ng/httpBackendSpec.js @@ -244,12 +244,8 @@ describe('$httpBackend', function() { it('should set up event listeners', function() { var progressFn = function() {}; var uploadProgressFn = function() {}; - $backend('GET', '/url', null, callback, {}, null, null, null, { - progress: progressFn, - upload: { - progress: uploadProgressFn - } - }); + $backend('GET', '/url', null, callback, {}, null, null, null, + {progress: progressFn}, {progress: uploadProgressFn}); xhr = MockXhr.$$lastInstance; expect(xhr.$$events.progress[0]).toBe(progressFn); expect(xhr.upload.$$events.progress[0]).toBe(uploadProgressFn); diff --git a/test/ng/httpSpec.js b/test/ng/httpSpec.js index 6ba6d2c75ea3..5c91f62b2b41 100644 --- a/test/ng/httpSpec.js +++ b/test/ng/httpSpec.js @@ -1,5 +1,7 @@ 'use strict'; +/* global MockXhr: false */ + describe('$http', function() { var callback, mockedCookies; @@ -1019,7 +1021,7 @@ describe('$http', function() { }); - describe('scope.$apply', function() { + describe('callbacks', function() { it('should $apply after success callback', function() { $httpBackend.when('GET').respond(200); @@ -1047,6 +1049,33 @@ describe('$http', function() { $exceptionHandler.errors = []; })); + + + it('should pass the event handlers through to the backend', function() { + var progressFn = jasmine.createSpy('progressFn'); + var uploadProgressFn = jasmine.createSpy('uploadProgressFn'); + $httpBackend.when('GET').respond(200); + $http({ + method: 'GET', + url: '/some', + eventHandlers: {progress: progressFn}, + uploadEventHandlers: {progress: uploadProgressFn} + }); + $rootScope.$apply(); + var mockXHR = MockXhr.$$lastInstance; + expect(mockXHR.$$events.progress).toEqual(jasmine.any(Function)); + expect(mockXHR.upload.$$events.progress).toEqual(jasmine.any(Function)); + + spyOn($rootScope, '$digest'); + + mockXHR.$$events.progress(); + expect(progressFn).toHaveBeenCalledOnce(); + expect($rootScope.$digest).toHaveBeenCalledTimes(1); + + mockXHR.upload.$$events.progress(); + expect(uploadProgressFn).toHaveBeenCalledOnce(); + expect($rootScope.$digest).toHaveBeenCalledTimes(2); + }); });