Skip to content

Commit

Permalink
feat($http): support handling additional XHR events
Browse files Browse the repository at this point in the history
  • Loading branch information
petebacondarwin committed Apr 8, 2016
1 parent 56c861c commit 01b1845
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 24 deletions.
30 changes: 27 additions & 3 deletions src/ng/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -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** –
Expand Down Expand Up @@ -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():
Expand Down
20 changes: 7 additions & 13 deletions src/ng/httpBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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;
Expand Down
8 changes: 7 additions & 1 deletion src/ngMock/angular-mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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] = [];
Expand Down
8 changes: 2 additions & 6 deletions test/ng/httpBackendSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
31 changes: 30 additions & 1 deletion test/ng/httpSpec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

/* global MockXhr: false */

describe('$http', function() {

var callback, mockedCookies;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
});
});


Expand Down

0 comments on commit 01b1845

Please sign in to comment.