Skip to content

Commit 26897bd

Browse files
Michael Krotscheckgkalpak
Michael Krotscheck
authored andcommitted
feat($resource): add support for request and requestError interceptors
This patch adds `request` and `requestError` interceptors for `$resource`, as per the documentation found for `$http` interceptors. It is important to note that returning an error at this stage of the request - before the call to `$http` - will completely bypass any global interceptors and/or recovery handlers, as those are added to a separate context. This is intentional; intercepting a request before it is passed to `$http` indicates that the resource itself has made a decision, and that it accepts the responsibility for recovery. Closes angular#5146
1 parent 5e64d2a commit 26897bd

File tree

2 files changed

+282
-9
lines changed

2 files changed

+282
-9
lines changed

src/ngResource/resource.js

+16-5
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,11 @@ function shallowClearAndCopy(src, dst) {
185185
* for more information.
186186
* - **`responseType`** - `{string}` - see
187187
* [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType).
188-
* - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
189-
* `response` and `responseError`. Both `response` and `responseError` interceptors get called
190-
* with `http response` object. See {@link ng.$http $http interceptors}. In addition, the
188+
* - **`interceptor`** - `{Object=}` - The interceptor object has four optional methods -
189+
* `request`, `requestError`, `response`, and `responseError`. See
190+
* {@link ng.$http $http interceptors} for details. In addition, the
191191
* resource instance or array object is accessible by the `resource` property of the
192-
* `http response` object.
192+
* `http response` object passed to response interceptors.
193193
* Keep in mind that the associated promise will be resolved with the value returned by the
194194
* response interceptor, if one is specified. The default response interceptor returns
195195
* `response.resource` (i.e. the resource instance or array).
@@ -707,6 +707,10 @@ angular.module('ngResource', ['ng']).
707707
var isInstanceCall = this instanceof Resource;
708708
var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
709709
var httpConfig = {};
710+
var requestInterceptor = action.interceptor && action.interceptor.request ||
711+
undefined;
712+
var requestErrorInterceptor = action.interceptor && action.interceptor.requestError ||
713+
undefined;
710714
var responseInterceptor = action.interceptor && action.interceptor.response ||
711715
defaultResponseInterceptor;
712716
var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
@@ -743,7 +747,14 @@ angular.module('ngResource', ['ng']).
743747
extend({}, extractParams(data, action.params || {}), params),
744748
action.url);
745749

746-
var promise = $http(httpConfig).then(function(response) {
750+
// Start the promise chain
751+
var promise = $q.
752+
resolve(httpConfig).
753+
then(requestInterceptor).
754+
catch(requestErrorInterceptor).
755+
then($http);
756+
757+
promise = promise.then(function(response) {
747758
var data = response.data;
748759

749760
if (data) {

test/ngResource/resourceSpec.js

+266-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
describe('resource', function() {
44

55
describe('basic usage', function() {
6-
var $resource, CreditCard, callback, $httpBackend, resourceProvider;
6+
var $resource, CreditCard, callback, $httpBackend, resourceProvider, $q;
77

88
beforeEach(module('ngResource'));
99

@@ -14,6 +14,7 @@ describe('basic usage', function() {
1414
beforeEach(inject(function($injector) {
1515
$httpBackend = $injector.get('$httpBackend');
1616
$resource = $injector.get('$resource');
17+
$q = $injector.get('$q');
1718
CreditCard = $resource('/CreditCard/:id:verb', {id:'@id.key'}, {
1819
charge:{
1920
method:'post',
@@ -1129,6 +1130,187 @@ describe('basic usage', function() {
11291130
});
11301131

11311132

1133+
describe('requestInterceptor', function() {
1134+
var rejectReason = {'lol':'cat'};
1135+
var successSpy, failureSpy;
1136+
1137+
beforeEach(function() {
1138+
successSpy = jasmine.createSpy('successSpy');
1139+
failureSpy = jasmine.createSpy('failureSpy');
1140+
});
1141+
1142+
it('should allow per action request interceptor that gets full configuration', function() {
1143+
var CreditCard = $resource('/CreditCard', {}, {
1144+
query: {
1145+
method: 'get',
1146+
isArray: true,
1147+
interceptor: {
1148+
request: function(httpConfig) {
1149+
callback(httpConfig);
1150+
return httpConfig;
1151+
}
1152+
}
1153+
}
1154+
});
1155+
1156+
$httpBackend.expect('GET', '/CreditCard').respond([{id: 1}]);
1157+
1158+
var resource = CreditCard.query();
1159+
resource.$promise.then(successSpy, failureSpy);
1160+
1161+
$httpBackend.flush();
1162+
expect(callback).toHaveBeenCalledOnce();
1163+
expect(successSpy).toHaveBeenCalledOnce();
1164+
expect(failureSpy).not.toHaveBeenCalled();
1165+
1166+
expect(callback).toHaveBeenCalledWith({
1167+
'method': 'get',
1168+
'url': '/CreditCard'
1169+
});
1170+
});
1171+
1172+
it('should call $http with the value returned from requestInterceptor', function() {
1173+
var CreditCard = $resource('/CreditCard', {}, {
1174+
query: {
1175+
method: 'get',
1176+
isArray: true,
1177+
interceptor: {
1178+
request: function(httpConfig) {
1179+
httpConfig.url = '/DebitCard';
1180+
return httpConfig;
1181+
}
1182+
}
1183+
}
1184+
});
1185+
1186+
$httpBackend.expect('GET', '/DebitCard').respond([{id: 1}]);
1187+
1188+
var resource = CreditCard.query();
1189+
resource.$promise.then(successSpy, failureSpy);
1190+
1191+
$httpBackend.flush();
1192+
expect(successSpy).toHaveBeenCalledOnce();
1193+
expect(failureSpy).not.toHaveBeenCalled();
1194+
});
1195+
1196+
it('should abort the operation if the requestInterceptor rejects the operation', function() {
1197+
var CreditCard = $resource('/CreditCard', {}, {
1198+
query: {
1199+
method: 'get',
1200+
isArray: true,
1201+
interceptor: {
1202+
request: function() {
1203+
return $q.reject(rejectReason);
1204+
}
1205+
}
1206+
}
1207+
});
1208+
1209+
var resource = CreditCard.query();
1210+
resource.$promise.then(successSpy, failureSpy);
1211+
1212+
// Make sure all promises resolve.
1213+
$rootScope.$apply();
1214+
1215+
// Ensure the resource promise was rejected
1216+
expect(resource.$resolved).toBeTruthy();
1217+
expect(successSpy).not.toHaveBeenCalled();
1218+
expect(failureSpy).toHaveBeenCalledOnce();
1219+
expect(failureSpy).toHaveBeenCalledWith(rejectReason);
1220+
1221+
// Ensure that no requests were made.
1222+
$httpBackend.verifyNoOutstandingRequest();
1223+
});
1224+
1225+
it('should call requestErrorInterceptor if requestInterceptor rejects the operation', function() {
1226+
var CreditCard = $resource('/CreditCard', {}, {
1227+
query: {
1228+
method: 'get',
1229+
isArray: true,
1230+
interceptor: {
1231+
request: function() {
1232+
return $q.reject(rejectReason);
1233+
},
1234+
requestError: function(rejection) {
1235+
callback(rejection);
1236+
return $q.reject(rejection);
1237+
}
1238+
}
1239+
}
1240+
});
1241+
1242+
var resource = CreditCard.query();
1243+
resource.$promise.then(successSpy, failureSpy);
1244+
$rootScope.$digest();
1245+
1246+
expect(callback).toHaveBeenCalledOnce();
1247+
expect(callback).toHaveBeenCalledWith(rejectReason);
1248+
expect(successSpy).not.toHaveBeenCalled();
1249+
expect(failureSpy).toHaveBeenCalledOnce();
1250+
expect(failureSpy).toHaveBeenCalledWith(rejectReason);
1251+
1252+
// Ensure that no requests were made.
1253+
$httpBackend.verifyNoOutstandingRequest();
1254+
});
1255+
1256+
it('should abort the operation if a requestErrorInterceptor rejects the operation', function() {
1257+
var CreditCard = $resource('/CreditCard', {}, {
1258+
query: {
1259+
method: 'get',
1260+
isArray: true,
1261+
interceptor: {
1262+
request: function() {
1263+
return $q.reject(rejectReason);
1264+
},
1265+
requestError: function(rejection) {
1266+
return $q.reject(rejection);
1267+
}
1268+
}
1269+
}
1270+
});
1271+
1272+
var resource = CreditCard.query();
1273+
resource.$promise.then(successSpy, failureSpy);
1274+
$rootScope.$apply();
1275+
1276+
expect(resource.$resolved).toBeTruthy();
1277+
expect(successSpy).not.toHaveBeenCalled();
1278+
expect(failureSpy).toHaveBeenCalledOnce();
1279+
expect(failureSpy).toHaveBeenCalledWith(rejectReason);
1280+
1281+
// Ensure that no requests were made.
1282+
$httpBackend.verifyNoOutstandingRequest();
1283+
});
1284+
1285+
it('should continue the operation if a requestErrorInterceptor rescues it', function() {
1286+
var CreditCard = $resource('/CreditCard', {}, {
1287+
query: {
1288+
method: 'get',
1289+
isArray: true,
1290+
interceptor: {
1291+
request: function(httpConfig) {
1292+
return $q.reject(httpConfig);
1293+
},
1294+
requestError: function(httpConfig) {
1295+
return $q.resolve(httpConfig);
1296+
}
1297+
}
1298+
}
1299+
});
1300+
1301+
$httpBackend.expect('GET', '/CreditCard').respond([{id: 1}]);
1302+
1303+
var resource = CreditCard.query();
1304+
resource.$promise.then(successSpy, failureSpy);
1305+
$httpBackend.flush();
1306+
1307+
expect(resource.$resolved).toBeTruthy();
1308+
expect(successSpy).toHaveBeenCalledOnce();
1309+
expect(failureSpy).not.toHaveBeenCalled();
1310+
$httpBackend.verifyNoOutstandingRequest();
1311+
});
1312+
});
1313+
11321314
it('should allow per action response interceptor that gets full response', function() {
11331315
CreditCard = $resource('/CreditCard', {}, {
11341316
query: {
@@ -1584,6 +1766,7 @@ describe('extra params', function() {
15841766
var $http;
15851767
var $httpBackend;
15861768
var $resource;
1769+
var $rootScope;
15871770

15881771
beforeEach(module('ngResource'));
15891772

@@ -1593,10 +1776,11 @@ describe('extra params', function() {
15931776
});
15941777
}));
15951778

1596-
beforeEach(inject(function(_$http_, _$httpBackend_, _$resource_) {
1779+
beforeEach(inject(function(_$http_, _$httpBackend_, _$resource_, _$rootScope_) {
15971780
$http = _$http_;
15981781
$httpBackend = _$httpBackend_;
15991782
$resource = _$resource_;
1783+
$rootScope = _$rootScope_;
16001784
}));
16011785

16021786
afterEach(function() {
@@ -1610,6 +1794,7 @@ describe('extra params', function() {
16101794
var R = $resource('/:foo');
16111795
R.get({foo: 'bar', baz: 'qux'});
16121796

1797+
$rootScope.$digest();
16131798
expect($http).toHaveBeenCalledWith(jasmine.objectContaining({params: {baz: 'qux'}}));
16141799
});
16151800

@@ -1624,7 +1809,7 @@ describe('extra params', function() {
16241809
});
16251810

16261811
describe('errors', function() {
1627-
var $httpBackend, $resource, $q;
1812+
var $httpBackend, $resource, $q, $rootScope;
16281813

16291814
beforeEach(module(function($exceptionHandlerProvider) {
16301815
$exceptionHandlerProvider.mode('log');
@@ -1636,6 +1821,7 @@ describe('errors', function() {
16361821
$httpBackend = $injector.get('$httpBackend');
16371822
$resource = $injector.get('$resource');
16381823
$q = $injector.get('$q');
1824+
$rootScope = $injector.get('$rootScope');
16391825
}));
16401826

16411827

@@ -1838,6 +2024,81 @@ describe('handling rejections', function() {
18382024
expect($exceptionHandler.errors[0]).toMatch(/^Error: should be caught/);
18392025
}
18402026
);
2027+
2028+
describe('requestInterceptor', function() {
2029+
var rejectReason = {'lol':'cat'};
2030+
var $q, $rootScope;
2031+
var successSpy, failureSpy, callback;
2032+
2033+
beforeEach(inject(function(_$q_, _$rootScope_) {
2034+
$q = _$q_;
2035+
$rootScope = _$rootScope_;
2036+
2037+
successSpy = jasmine.createSpy('successSpy');
2038+
failureSpy = jasmine.createSpy('failureSpy');
2039+
callback = jasmine.createSpy();
2040+
}));
2041+
2042+
it('should call requestErrorInterceptor if requestInterceptor throws an error', function() {
2043+
var CreditCard = $resource('/CreditCard', {}, {
2044+
query: {
2045+
method: 'get',
2046+
isArray: true,
2047+
interceptor: {
2048+
request: function() {
2049+
throw rejectReason;
2050+
},
2051+
requestError: function(rejection) {
2052+
callback(rejection);
2053+
return $q.reject(rejection);
2054+
}
2055+
}
2056+
}
2057+
});
2058+
2059+
var resource = CreditCard.query();
2060+
resource.$promise.then(successSpy, failureSpy);
2061+
$rootScope.$apply();
2062+
2063+
expect(callback).toHaveBeenCalledOnce();
2064+
expect(callback).toHaveBeenCalledWith(rejectReason);
2065+
expect(successSpy).not.toHaveBeenCalled();
2066+
expect(failureSpy).toHaveBeenCalledOnce();
2067+
expect(failureSpy).toHaveBeenCalledWith(rejectReason);
2068+
2069+
// Ensure that no requests were made.
2070+
$httpBackend.verifyNoOutstandingRequest();
2071+
});
2072+
2073+
it('should abort the operation if a requestErrorInterceptor throws an exception', function() {
2074+
var CreditCard = $resource('/CreditCard', {}, {
2075+
query: {
2076+
method: 'get',
2077+
isArray: true,
2078+
interceptor: {
2079+
request: function() {
2080+
return $q.reject();
2081+
},
2082+
requestError: function() {
2083+
throw rejectReason;
2084+
}
2085+
}
2086+
}
2087+
});
2088+
2089+
var resource = CreditCard.query();
2090+
resource.$promise.then(successSpy, failureSpy);
2091+
$rootScope.$apply();
2092+
2093+
expect(resource.$resolved).toBeTruthy();
2094+
expect(successSpy).not.toHaveBeenCalled();
2095+
expect(failureSpy).toHaveBeenCalledOnce();
2096+
expect(failureSpy).toHaveBeenCalledWith(rejectReason);
2097+
2098+
// Ensure that no requests were made.
2099+
$httpBackend.verifyNoOutstandingRequest();
2100+
});
2101+
});
18412102
});
18422103

18432104
describe('cancelling requests', function() {
@@ -1902,7 +2163,7 @@ describe('cancelling requests', function() {
19022163
);
19032164

19042165
it('should use `cancellable` value if passed a non-numeric `timeout` in an action',
1905-
inject(function($log, $q) {
2166+
inject(function($log, $q, $rootScope) {
19062167
spyOn($log, 'debug');
19072168
$httpBackend.whenGET('/CreditCard').respond({});
19082169

@@ -1915,6 +2176,7 @@ describe('cancelling requests', function() {
19152176
});
19162177

19172178
var creditCard = CreditCard.get();
2179+
$rootScope.$digest();
19182180
expect(creditCard.$cancelRequest).toBeDefined();
19192181
expect(httpSpy.calls.argsFor(0)[0].timeout).toEqual(jasmine.any($q));
19202182
expect(httpSpy.calls.argsFor(0)[0].timeout.then).toBeDefined();

0 commit comments

Comments
 (0)