Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit dba6bc7

Browse files
committed
feat($resource): expose promise based api via $then and $resolved
Expose $then and $resolved properties on resource action return values which allow checking if a promise has been resolved already as well as registering listeners at any time of the resource object life-cycle. This commit replaces unreleased commit f3bff27 which exposed unintuitive $q api instead and didn't expose important stuff like http headers.
1 parent c0a0781 commit dba6bc7

File tree

2 files changed

+238
-64
lines changed

2 files changed

+238
-64
lines changed

src/ngResource/resource.js

+41-24
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* the need to interact with the low level {@link ng.$http $http} service.
2020
*
2121
* @param {string} url A parameterized URL template with parameters prefixed by `:` as in
22-
* `/user/:username`. If you are using a URL with a port number (e.g.
22+
* `/user/:username`. If you are using a URL with a port number (e.g.
2323
* `http://example.com:8080/api`), you'll need to escape the colon character before the port
2424
* number, like this: `$resource('http://example.com\\:8080/api')`.
2525
*
@@ -109,10 +109,23 @@
109109
* - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
110110
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
111111
*
112-
* The Resource also has these properties:
113112
*
114-
* - '$q': the promise from the underlying {@link ng.$http} call.
115-
* - '$resolved': true if the promise has been resolved (either with success or rejection);
113+
* The Resource instances and collection have these additional properties:
114+
*
115+
* - `$then`: the `then` method of a {@link ng.$q promise} derived from the underlying
116+
* {@link ng.$http $http} call.
117+
*
118+
* The success callback for the `$then` method will be resolved if the underlying `$http` requests
119+
* succeeds.
120+
*
121+
* The success callback is called with a single object which is the {@link ng.$http http response}
122+
* object extended with a new property `resource`. This `resource` property is a reference to the
123+
* result of the resource action — resource object or array of resources.
124+
*
125+
* The error callback is called with the {@link ng.$http http response} object when an http
126+
* error occurs.
127+
*
128+
* - `$resolved`: true if the promise has been resolved (either with success or rejection);
116129
* Knowing if the Resource has been resolved is useful in data-binding.
117130
*
118131
* @example
@@ -415,32 +428,36 @@ angular.module('ngResource', ['ng']).
415428
httpConfig.data = data;
416429
httpConfig.url = route.url(extend({}, extractParams(data, action.params || {}), params))
417430

418-
function markResolved() { value.$resolved = true; };
431+
function markResolved() { value.$resolved = true; }
419432

420433
promise = $http(httpConfig);
421-
value.$q = promise;
422434
value.$resolved = false;
423-
promise.then(markResolved, markResolved)
424-
promise.then(function(response) {
425-
var data = response.data;
426-
var q = value.$q, resolved = value.$resolved;
427-
if (data) {
428-
if (action.isArray) {
429-
value.length = 0;
430-
forEach(data, function(item) {
431-
value.push(new Resource(item));
432-
});
433-
} else {
434-
copy(data, value);
435-
value.$q = q;
436-
value.$resolved = resolved;
437-
}
435+
436+
promise.then(markResolved, markResolved);
437+
value.$then = promise.then(function(response) {
438+
var data = response.data;
439+
var then = value.$then, resolved = value.$resolved;
440+
441+
if (data) {
442+
if (action.isArray) {
443+
value.length = 0;
444+
forEach(data, function(item) {
445+
value.push(new Resource(item));
446+
});
447+
} else {
448+
copy(data, value);
449+
value.$then = then;
450+
value.$resolved = resolved;
438451
}
439-
(success||noop)(value, response.headers);
440-
}, error);
452+
}
441453

442-
return value;
454+
(success||noop)(value, response.headers);
443455

456+
response.resource = value;
457+
return response;
458+
}, error).then;
459+
460+
return value;
444461
};
445462

446463

test/ngResource/resourceSpec.js

+197-40
Original file line numberDiff line numberDiff line change
@@ -419,51 +419,208 @@ describe("resource", function() {
419419
expect(person.name).toEqual('misko');
420420
});
421421

422-
it("should have $q and $resolved properties for get", function () {
423-
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
424-
var cc = CreditCard.get({id: 123}, callback);
425-
expect(cc.$q).toBeDefined();
426-
expect(typeof cc.$q).toBe('object');
427-
expect(cc.$resolved).toBeFalsy();
428-
$httpBackend.flush();
429-
expect(cc.$q).toBeDefined();
430-
expect(cc.$resolved).toBeTruthy();
431-
});
432422

433-
it("should have $q and $resolved properties for query", function() {
434-
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
423+
describe('promise api', function() {
435424

436-
var ccs = CreditCard.query({key: 'value'}, callback);
437-
expect(ccs.$q).toBeDefined();
438-
expect(typeof ccs.$q).toBe('object');
439-
expect(ccs.$resolved).toBeFalsy();
440-
$httpBackend.flush();
441-
expect(ccs.$q).toBeDefined();
442-
expect(ccs.$resolved).toBeTruthy();
443-
});
425+
var $rootScope;
444426

445-
it("should have $q and $resolved properties for save", function() {
446-
$httpBackend.expect('POST', '/CreditCard/123', '{"id":{"key":123},"name":"misko"}').
447-
respond({id: {key: 123}, name: 'rama'});
448427

449-
var cc = CreditCard.save({id: {key: 123}, name: 'misko'}, callback);
450-
expect(cc.$q).toBeDefined();
451-
expect(typeof cc.$q).toBe('object');
452-
expect(cc.$resolved).toBeFalsy();
453-
$httpBackend.flush();
454-
expect(cc.$q).toBeDefined();
455-
expect(cc.$resolved).toBeTruthy();
456-
});
428+
beforeEach(inject(function(_$rootScope_) {
429+
$rootScope = _$rootScope_;
430+
}));
457431

458-
it('should should have $q and $resolved properties for delete', function() {
459-
$httpBackend.expect('DELETE', '/CreditCard/123').respond({});
460-
var removed = CreditCard.remove({id:123}, callback);
461-
expect(removed.$q).toBeDefined();
462-
expect(typeof removed.$q).toBe('object');
463-
expect(removed.$resolved).toBeFalsy();
464-
$httpBackend.flush();
465-
expect(removed.$q).toBeDefined();
466-
expect(removed.$resolved).toBeTruthy();
432+
433+
describe('single resource', function() {
434+
435+
it('should add promise $then method to the result object', function() {
436+
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
437+
var cc = CreditCard.get({id: 123});
438+
439+
cc.$then(callback);
440+
expect(callback).not.toHaveBeenCalled();
441+
442+
$httpBackend.flush();
443+
444+
var response = callback.mostRecentCall.args[0];
445+
446+
expect(response).toEqualData({
447+
data: {id: 123, number: '9876'},
448+
status: 200,
449+
config: {method: 'GET', data: undefined, url: '/CreditCard/123'},
450+
resource: {id: 123, number: '9876', $resolved: true}
451+
});
452+
expect(typeof response.resource.$save).toBe('function');
453+
});
454+
455+
456+
it('should keep $then around after promise resolution', function() {
457+
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
458+
var cc = CreditCard.get({id: 123});
459+
460+
cc.$then(callback);
461+
$httpBackend.flush();
462+
463+
var response = callback.mostRecentCall.args[0];
464+
465+
callback.reset();
466+
467+
cc.$then(callback);
468+
$rootScope.$apply(); //flush async queue
469+
470+
expect(callback).toHaveBeenCalledOnceWith(response);
471+
});
472+
473+
474+
it('should allow promise chaining via $then method', function() {
475+
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
476+
var cc = CreditCard.get({id: 123});
477+
478+
cc.$then(function(response) { return 'new value'; }).then(callback);
479+
$httpBackend.flush();
480+
481+
expect(callback).toHaveBeenCalledOnceWith('new value');
482+
});
483+
484+
485+
it('should allow error callback registration via $then method', function() {
486+
$httpBackend.expect('GET', '/CreditCard/123').respond(404, 'resource not found');
487+
var cc = CreditCard.get({id: 123});
488+
489+
cc.$then(null, callback);
490+
$httpBackend.flush();
491+
492+
var response = callback.mostRecentCall.args[0];
493+
494+
expect(response).toEqualData({
495+
data : 'resource not found',
496+
status : 404,
497+
config : { method : 'GET', data : undefined, url : '/CreditCard/123' }
498+
});
499+
});
500+
501+
502+
it('should add $resolved boolean field to the result object', function() {
503+
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
504+
var cc = CreditCard.get({id: 123});
505+
506+
expect(cc.$resolved).toBe(false);
507+
508+
cc.$then(callback);
509+
expect(cc.$resolved).toBe(false);
510+
511+
$httpBackend.flush();
512+
513+
expect(cc.$resolved).toBe(true);
514+
});
515+
516+
517+
it('should set $resolved field to true when an error occurs', function() {
518+
$httpBackend.expect('GET', '/CreditCard/123').respond(404, 'resource not found');
519+
var cc = CreditCard.get({id: 123});
520+
521+
cc.$then(null, callback);
522+
$httpBackend.flush();
523+
expect(callback).toHaveBeenCalledOnce();
524+
expect(cc.$resolved).toBe(true);
525+
});
526+
});
527+
528+
529+
describe('resource collection', function() {
530+
531+
it('should add promise $then method to the result object', function() {
532+
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
533+
var ccs = CreditCard.query({key: 'value'});
534+
535+
ccs.$then(callback);
536+
expect(callback).not.toHaveBeenCalled();
537+
538+
$httpBackend.flush();
539+
540+
var response = callback.mostRecentCall.args[0];
541+
542+
expect(response).toEqualData({
543+
data: [{id: 1}, {id :2}],
544+
status: 200,
545+
config: {method: 'GET', data: undefined, url: '/CreditCard?key=value'},
546+
resource: [ { id : 1 }, { id : 2 } ]
547+
});
548+
expect(typeof response.resource[0].$save).toBe('function');
549+
expect(typeof response.resource[1].$save).toBe('function');
550+
});
551+
552+
553+
it('should keep $then around after promise resolution', function() {
554+
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
555+
var ccs = CreditCard.query({key: 'value'});
556+
557+
ccs.$then(callback);
558+
$httpBackend.flush();
559+
560+
var response = callback.mostRecentCall.args[0];
561+
562+
callback.reset();
563+
564+
ccs.$then(callback);
565+
$rootScope.$apply(); //flush async queue
566+
567+
expect(callback).toHaveBeenCalledOnceWith(response);
568+
});
569+
570+
571+
it('should allow promise chaining via $then method', function() {
572+
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
573+
var ccs = CreditCard.query({key: 'value'});
574+
575+
ccs.$then(function(response) { return 'new value'; }).then(callback);
576+
$httpBackend.flush();
577+
578+
expect(callback).toHaveBeenCalledOnceWith('new value');
579+
});
580+
581+
582+
it('should allow error callback registration via $then method', function() {
583+
$httpBackend.expect('GET', '/CreditCard?key=value').respond(404, 'resource not found');
584+
var ccs = CreditCard.query({key: 'value'});
585+
586+
ccs.$then(null, callback);
587+
$httpBackend.flush();
588+
589+
var response = callback.mostRecentCall.args[0];
590+
591+
expect(response).toEqualData({
592+
data : 'resource not found',
593+
status : 404,
594+
config : { method : 'GET', data : undefined, url : '/CreditCard?key=value' }
595+
});
596+
});
597+
598+
599+
it('should add $resolved boolean field to the result object', function() {
600+
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
601+
var ccs = CreditCard.query({key: 'value'}, callback);
602+
603+
expect(ccs.$resolved).toBe(false);
604+
605+
ccs.$then(callback);
606+
expect(ccs.$resolved).toBe(false);
607+
608+
$httpBackend.flush();
609+
610+
expect(ccs.$resolved).toBe(true);
611+
});
612+
613+
614+
it('should set $resolved field to true when an error occurs', function() {
615+
$httpBackend.expect('GET', '/CreditCard?key=value').respond(404, 'resource not found');
616+
var ccs = CreditCard.query({key: 'value'});
617+
618+
ccs.$then(null, callback);
619+
$httpBackend.flush();
620+
expect(callback).toHaveBeenCalledOnce();
621+
expect(ccs.$resolved).toBe(true);
622+
});
623+
});
467624
});
468625

469626

0 commit comments

Comments
 (0)