Skip to content

Commit cc80bd9

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 1558128 commit cc80bd9

File tree

2 files changed

+282
-8
lines changed

2 files changed

+282
-8
lines changed

src/ngResource/resource.js

+15-4
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,9 @@ function shallowClearAndCopy(src, dst) {
190190
* for more information.
191191
* - **`responseType`** - `{string}` - see
192192
* [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType).
193-
* - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
194-
* `response` and `responseError`. Both `response` and `responseError` interceptors get called
195-
* with `http response` object. See {@link ng.$http $http interceptors}.
193+
* - **`interceptor`** - `{Object=}` - The interceptor object has four optional methods -
194+
* `request`, `requestError`, `response`, and `responseError`. See
195+
* {@link ng.$http $http interceptors} for details.
196196
*
197197
* @param {Object} options Hash with custom settings that should extend the
198198
* default `$resourceProvider` behavior. The supported options are:
@@ -699,6 +699,10 @@ angular.module('ngResource', ['ng']).
699699
var isInstanceCall = this instanceof Resource;
700700
var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
701701
var httpConfig = {};
702+
var requestInterceptor = action.interceptor && action.interceptor.request ||
703+
undefined;
704+
var requestErrorInterceptor = action.interceptor && action.interceptor.requestError ||
705+
undefined;
702706
var responseInterceptor = action.interceptor && action.interceptor.response ||
703707
defaultResponseInterceptor;
704708
var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
@@ -735,7 +739,14 @@ angular.module('ngResource', ['ng']).
735739
extend({}, extractParams(data, action.params || {}), params),
736740
action.url);
737741

738-
var promise = $http(httpConfig).then(function(response) {
742+
// Start the promise chain
743+
var promise = $q.
744+
resolve(httpConfig).
745+
then(requestInterceptor).
746+
catch(requestErrorInterceptor).
747+
then($http);
748+
749+
promise = promise.then(function(response) {
739750
var data = response.data;
740751

741752
if (data) {

test/ngResource/resourceSpec.js

+267-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',
@@ -1083,6 +1084,187 @@ describe('basic usage', function() {
10831084
});
10841085

10851086

1087+
describe('requestInterceptor', function() {
1088+
var rejectReason = {'lol':'cat'};
1089+
var successSpy, failureSpy;
1090+
1091+
beforeEach(function() {
1092+
successSpy = jasmine.createSpy('successSpy');
1093+
failureSpy = jasmine.createSpy('failureSpy');
1094+
});
1095+
1096+
it('should allow per action request interceptor that gets full configuration', function() {
1097+
var CreditCard = $resource('/CreditCard', {}, {
1098+
query: {
1099+
method: 'get',
1100+
isArray: true,
1101+
interceptor: {
1102+
request: function(httpConfig) {
1103+
callback(httpConfig);
1104+
return httpConfig;
1105+
}
1106+
}
1107+
}
1108+
});
1109+
1110+
$httpBackend.expect('GET', '/CreditCard').respond([{id: 1}]);
1111+
1112+
var resource = CreditCard.query();
1113+
resource.$promise.then(successSpy, failureSpy);
1114+
1115+
$httpBackend.flush();
1116+
expect(callback).toHaveBeenCalledOnce();
1117+
expect(successSpy).toHaveBeenCalledOnce();
1118+
expect(failureSpy).not.toHaveBeenCalled();
1119+
1120+
expect(callback).toHaveBeenCalledWith({
1121+
'method': 'get',
1122+
'url': '/CreditCard'
1123+
});
1124+
});
1125+
1126+
it('should call $http with the value returned from requestInterceptor', function() {
1127+
var CreditCard = $resource('/CreditCard', {}, {
1128+
query: {
1129+
method: 'get',
1130+
isArray: true,
1131+
interceptor: {
1132+
request: function(httpConfig) {
1133+
httpConfig.url = '/DebitCard';
1134+
return httpConfig;
1135+
}
1136+
}
1137+
}
1138+
});
1139+
1140+
$httpBackend.expect('GET', '/DebitCard').respond([{id: 1}]);
1141+
1142+
var resource = CreditCard.query();
1143+
resource.$promise.then(successSpy, failureSpy);
1144+
1145+
$httpBackend.flush();
1146+
expect(successSpy).toHaveBeenCalledOnce();
1147+
expect(failureSpy).not.toHaveBeenCalled();
1148+
});
1149+
1150+
it('should abort the operation if the requestInterceptor rejects the operation', function() {
1151+
var CreditCard = $resource('/CreditCard', {}, {
1152+
query: {
1153+
method: 'get',
1154+
isArray: true,
1155+
interceptor: {
1156+
request: function() {
1157+
return $q.reject(rejectReason);
1158+
}
1159+
}
1160+
}
1161+
});
1162+
1163+
var resource = CreditCard.query();
1164+
resource.$promise.then(successSpy, failureSpy);
1165+
1166+
// Make sure all promises resolve.
1167+
$rootScope.$apply();
1168+
1169+
// Ensure the resource promise was rejected
1170+
expect(resource.$resolved).toBeTruthy();
1171+
expect(successSpy).not.toHaveBeenCalled();
1172+
expect(failureSpy).toHaveBeenCalledOnce();
1173+
expect(failureSpy).toHaveBeenCalledWith(rejectReason);
1174+
1175+
// Ensure that no requests were made.
1176+
$httpBackend.verifyNoOutstandingRequest();
1177+
});
1178+
1179+
it('should call requestErrorInterceptor if requestInterceptor rejects the operation', function() {
1180+
var CreditCard = $resource('/CreditCard', {}, {
1181+
query: {
1182+
method: 'get',
1183+
isArray: true,
1184+
interceptor: {
1185+
request: function() {
1186+
return $q.reject(rejectReason);
1187+
},
1188+
requestError: function(rejection) {
1189+
callback(rejection);
1190+
return $q.reject(rejection);
1191+
}
1192+
}
1193+
}
1194+
});
1195+
1196+
var resource = CreditCard.query();
1197+
resource.$promise.then(successSpy, failureSpy);
1198+
$rootScope.$digest();
1199+
1200+
expect(callback).toHaveBeenCalledOnce();
1201+
expect(callback).toHaveBeenCalledWith(rejectReason);
1202+
expect(successSpy).not.toHaveBeenCalled();
1203+
expect(failureSpy).toHaveBeenCalledOnce();
1204+
expect(failureSpy).toHaveBeenCalledWith(rejectReason);
1205+
1206+
// Ensure that no requests were made.
1207+
$httpBackend.verifyNoOutstandingRequest();
1208+
});
1209+
1210+
it('should abort the operation if a requestErrorInterceptor rejects the operation', function() {
1211+
var CreditCard = $resource('/CreditCard', {}, {
1212+
query: {
1213+
method: 'get',
1214+
isArray: true,
1215+
interceptor: {
1216+
request: function() {
1217+
return $q.reject(rejectReason);
1218+
},
1219+
requestError: function(rejection) {
1220+
return $q.reject(rejection);
1221+
}
1222+
}
1223+
}
1224+
});
1225+
1226+
var resource = CreditCard.query();
1227+
resource.$promise.then(successSpy, failureSpy);
1228+
$rootScope.$apply();
1229+
1230+
expect(resource.$resolved).toBeTruthy();
1231+
expect(successSpy).not.toHaveBeenCalled();
1232+
expect(failureSpy).toHaveBeenCalledOnce();
1233+
expect(failureSpy).toHaveBeenCalledWith(rejectReason);
1234+
1235+
// Ensure that no requests were made.
1236+
$httpBackend.verifyNoOutstandingRequest();
1237+
});
1238+
1239+
it('should continue the operation if a requestErrorInterceptor rescues it', function() {
1240+
var CreditCard = $resource('/CreditCard', {}, {
1241+
query: {
1242+
method: 'get',
1243+
isArray: true,
1244+
interceptor: {
1245+
request: function(httpConfig) {
1246+
return $q.reject(httpConfig);
1247+
},
1248+
requestError: function(httpConfig) {
1249+
return $q.resolve(httpConfig);
1250+
}
1251+
}
1252+
}
1253+
});
1254+
1255+
$httpBackend.expect('GET', '/CreditCard').respond([{id: 1}]);
1256+
1257+
var resource = CreditCard.query();
1258+
resource.$promise.then(successSpy, failureSpy);
1259+
$httpBackend.flush();
1260+
1261+
expect(resource.$resolved).toBeTruthy();
1262+
expect(successSpy).toHaveBeenCalledOnce();
1263+
expect(failureSpy).not.toHaveBeenCalled();
1264+
$httpBackend.verifyNoOutstandingRequest();
1265+
});
1266+
});
1267+
10861268
it('should allow per action response interceptor that gets full response', function() {
10871269
CreditCard = $resource('/CreditCard', {}, {
10881270
query: {
@@ -1537,6 +1719,7 @@ describe('extra params', function() {
15371719
var $http;
15381720
var $httpBackend;
15391721
var $resource;
1722+
var $rootScope;
15401723

15411724
beforeEach(module('ngResource'));
15421725

@@ -1546,10 +1729,11 @@ describe('extra params', function() {
15461729
});
15471730
}));
15481731

1549-
beforeEach(inject(function(_$http_, _$httpBackend_, _$resource_) {
1732+
beforeEach(inject(function(_$http_, _$httpBackend_, _$resource_, _$rootScope_) {
15501733
$http = _$http_;
15511734
$httpBackend = _$httpBackend_;
15521735
$resource = _$resource_;
1736+
$rootScope = _$rootScope_;
15531737
}));
15541738

15551739
afterEach(function() {
@@ -1563,6 +1747,7 @@ describe('extra params', function() {
15631747
var R = $resource('/:foo');
15641748
R.get({foo: 'bar', baz: 'qux'});
15651749

1750+
$rootScope.$digest();
15661751
expect($http).toHaveBeenCalledWith(jasmine.objectContaining({params: {baz: 'qux'}}));
15671752
});
15681753

@@ -1577,7 +1762,7 @@ describe('extra params', function() {
15771762
});
15781763

15791764
describe('errors', function() {
1580-
var $httpBackend, $resource, $q;
1765+
var $httpBackend, $resource, $q, $rootScope;
15811766

15821767
beforeEach(module(function($exceptionHandlerProvider) {
15831768
$exceptionHandlerProvider.mode('log');
@@ -1589,6 +1774,7 @@ describe('errors', function() {
15891774
$httpBackend = $injector.get('$httpBackend');
15901775
$resource = $injector.get('$resource');
15911776
$q = $injector.get('$q');
1777+
$rootScope = $injector.get('$rootScope');
15921778
}));
15931779

15941780

@@ -1721,6 +1907,82 @@ describe('handling rejections', function() {
17211907
expect($exceptionHandler.errors[0]).toMatch(/^Possibly unhandled rejection/);
17221908
})
17231909
);
1910+
1911+
1912+
describe('requestInterceptor', function() {
1913+
var rejectReason = {'lol':'cat'};
1914+
var $q, $rootScope;
1915+
var successSpy, failureSpy, callback;
1916+
1917+
beforeEach(inject(function(_$q_, _$rootScope_) {
1918+
$q = _$q_;
1919+
$rootScope = _$rootScope_;
1920+
1921+
successSpy = jasmine.createSpy('successSpy');
1922+
failureSpy = jasmine.createSpy('failureSpy');
1923+
callback = jasmine.createSpy();
1924+
}));
1925+
1926+
it('should call requestErrorInterceptor if requestInterceptor throws an error', function() {
1927+
var CreditCard = $resource('/CreditCard', {}, {
1928+
query: {
1929+
method: 'get',
1930+
isArray: true,
1931+
interceptor: {
1932+
request: function() {
1933+
throw rejectReason;
1934+
},
1935+
requestError: function(rejection) {
1936+
callback(rejection);
1937+
return $q.reject(rejection);
1938+
}
1939+
}
1940+
}
1941+
});
1942+
1943+
var resource = CreditCard.query();
1944+
resource.$promise.then(successSpy, failureSpy);
1945+
$rootScope.$apply();
1946+
1947+
expect(callback).toHaveBeenCalledOnce();
1948+
expect(callback).toHaveBeenCalledWith(rejectReason);
1949+
expect(successSpy).not.toHaveBeenCalled();
1950+
expect(failureSpy).toHaveBeenCalledOnce();
1951+
expect(failureSpy).toHaveBeenCalledWith(rejectReason);
1952+
1953+
// Ensure that no requests were made.
1954+
$httpBackend.verifyNoOutstandingRequest();
1955+
});
1956+
1957+
it('should abort the operation if a requestErrorInterceptor throws an exception', function() {
1958+
var CreditCard = $resource('/CreditCard', {}, {
1959+
query: {
1960+
method: 'get',
1961+
isArray: true,
1962+
interceptor: {
1963+
request: function() {
1964+
return $q.reject();
1965+
},
1966+
requestError: function() {
1967+
throw rejectReason;
1968+
}
1969+
}
1970+
}
1971+
});
1972+
1973+
var resource = CreditCard.query();
1974+
resource.$promise.then(successSpy, failureSpy);
1975+
$rootScope.$apply();
1976+
1977+
expect(resource.$resolved).toBeTruthy();
1978+
expect(successSpy).not.toHaveBeenCalled();
1979+
expect(failureSpy).toHaveBeenCalledOnce();
1980+
expect(failureSpy).toHaveBeenCalledWith(rejectReason);
1981+
1982+
// Ensure that no requests were made.
1983+
$httpBackend.verifyNoOutstandingRequest();
1984+
});
1985+
});
17241986
});
17251987

17261988
describe('cancelling requests', function() {
@@ -1785,7 +2047,7 @@ describe('cancelling requests', function() {
17852047
);
17862048

17872049
it('should use `cancellable` value if passed a non-numeric `timeout` in an action',
1788-
inject(function($log, $q) {
2050+
inject(function($log, $q, $rootScope) {
17892051
spyOn($log, 'debug');
17902052
$httpBackend.whenGET('/CreditCard').respond({});
17912053

@@ -1798,6 +2060,7 @@ describe('cancelling requests', function() {
17982060
});
17992061

18002062
var creditCard = CreditCard.get();
2063+
$rootScope.$digest();
18012064
expect(creditCard.$cancelRequest).toBeDefined();
18022065
expect(httpSpy.calls.argsFor(0)[0].timeout).toEqual(jasmine.any($q));
18032066
expect(httpSpy.calls.argsFor(0)[0].timeout.then).toBeDefined();

0 commit comments

Comments
 (0)