diff options
| author | Igor Minar | 2013-02-11 21:43:30 -0800 |
|---|---|---|
| committer | Igor Minar | 2013-02-11 22:24:21 -0800 |
| commit | dba6bc73e802fdae685a9f351d3e23c7efa8568a (patch) | |
| tree | 3c4ad521a61a469a68b956a2bcb52d95c71185ae | |
| parent | c0a0781425625f45e47f3523f115616aa9447f21 (diff) | |
| download | angular.js-dba6bc73e802fdae685a9f351d3e23c7efa8568a.tar.bz2 | |
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 f3bff27460afb3be208a05959d5b84233d34b7eb
which exposed unintuitive $q api instead and didn't expose important stuff
like http headers.
| -rw-r--r-- | src/ngResource/resource.js | 65 | ||||
| -rw-r--r-- | test/ngResource/resourceSpec.js | 237 |
2 files changed, 238 insertions, 64 deletions
diff --git a/src/ngResource/resource.js b/src/ngResource/resource.js index 037a12ae..0d5c2b39 100644 --- a/src/ngResource/resource.js +++ b/src/ngResource/resource.js @@ -19,7 +19,7 @@ * the need to interact with the low level {@link ng.$http $http} service. * * @param {string} url A parameterized URL template with parameters prefixed by `:` as in - * `/user/:username`. If you are using a URL with a port number (e.g. + * `/user/:username`. If you are using a URL with a port number (e.g. * `http://example.com:8080/api`), you'll need to escape the colon character before the port * number, like this: `$resource('http://example.com\\:8080/api')`. * @@ -109,10 +109,23 @@ * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` * - non-GET instance actions: `instance.$action([parameters], [success], [error])` * - * The Resource also has these properties: * - * - '$q': the promise from the underlying {@link ng.$http} call. - * - '$resolved': true if the promise has been resolved (either with success or rejection); + * The Resource instances and collection have these additional properties: + * + * - `$then`: the `then` method of a {@link ng.$q promise} derived from the underlying + * {@link ng.$http $http} call. + * + * The success callback for the `$then` method will be resolved if the underlying `$http` requests + * succeeds. + * + * The success callback is called with a single object which is the {@link ng.$http http response} + * object extended with a new property `resource`. This `resource` property is a reference to the + * result of the resource action — resource object or array of resources. + * + * The error callback is called with the {@link ng.$http http response} object when an http + * error occurs. + * + * - `$resolved`: true if the promise has been resolved (either with success or rejection); * Knowing if the Resource has been resolved is useful in data-binding. * * @example @@ -415,32 +428,36 @@ angular.module('ngResource', ['ng']). httpConfig.data = data; httpConfig.url = route.url(extend({}, extractParams(data, action.params || {}), params)) - function markResolved() { value.$resolved = true; }; + function markResolved() { value.$resolved = true; } promise = $http(httpConfig); - value.$q = promise; value.$resolved = false; - promise.then(markResolved, markResolved) - promise.then(function(response) { - var data = response.data; - var q = value.$q, resolved = value.$resolved; - if (data) { - if (action.isArray) { - value.length = 0; - forEach(data, function(item) { - value.push(new Resource(item)); - }); - } else { - copy(data, value); - value.$q = q; - value.$resolved = resolved; - } + + promise.then(markResolved, markResolved); + value.$then = promise.then(function(response) { + var data = response.data; + var then = value.$then, resolved = value.$resolved; + + if (data) { + if (action.isArray) { + value.length = 0; + forEach(data, function(item) { + value.push(new Resource(item)); + }); + } else { + copy(data, value); + value.$then = then; + value.$resolved = resolved; } - (success||noop)(value, response.headers); - }, error); + } - return value; + (success||noop)(value, response.headers); + response.resource = value; + return response; + }, error).then; + + return value; }; diff --git a/test/ngResource/resourceSpec.js b/test/ngResource/resourceSpec.js index ad9280f5..d0bcbefc 100644 --- a/test/ngResource/resourceSpec.js +++ b/test/ngResource/resourceSpec.js @@ -419,51 +419,208 @@ describe("resource", function() { expect(person.name).toEqual('misko'); }); - it("should have $q and $resolved properties for get", function () { - $httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'}); - var cc = CreditCard.get({id: 123}, callback); - expect(cc.$q).toBeDefined(); - expect(typeof cc.$q).toBe('object'); - expect(cc.$resolved).toBeFalsy(); - $httpBackend.flush(); - expect(cc.$q).toBeDefined(); - expect(cc.$resolved).toBeTruthy(); - }); - it("should have $q and $resolved properties for query", function() { - $httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]); + describe('promise api', function() { - var ccs = CreditCard.query({key: 'value'}, callback); - expect(ccs.$q).toBeDefined(); - expect(typeof ccs.$q).toBe('object'); - expect(ccs.$resolved).toBeFalsy(); - $httpBackend.flush(); - expect(ccs.$q).toBeDefined(); - expect(ccs.$resolved).toBeTruthy(); - }); + var $rootScope; - it("should have $q and $resolved properties for save", function() { - $httpBackend.expect('POST', '/CreditCard/123', '{"id":{"key":123},"name":"misko"}'). - respond({id: {key: 123}, name: 'rama'}); - var cc = CreditCard.save({id: {key: 123}, name: 'misko'}, callback); - expect(cc.$q).toBeDefined(); - expect(typeof cc.$q).toBe('object'); - expect(cc.$resolved).toBeFalsy(); - $httpBackend.flush(); - expect(cc.$q).toBeDefined(); - expect(cc.$resolved).toBeTruthy(); - }); + beforeEach(inject(function(_$rootScope_) { + $rootScope = _$rootScope_; + })); - it('should should have $q and $resolved properties for delete', function() { - $httpBackend.expect('DELETE', '/CreditCard/123').respond({}); - var removed = CreditCard.remove({id:123}, callback); - expect(removed.$q).toBeDefined(); - expect(typeof removed.$q).toBe('object'); - expect(removed.$resolved).toBeFalsy(); - $httpBackend.flush(); - expect(removed.$q).toBeDefined(); - expect(removed.$resolved).toBeTruthy(); + + describe('single resource', function() { + + it('should add promise $then method to the result object', function() { + $httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'}); + var cc = CreditCard.get({id: 123}); + + cc.$then(callback); + expect(callback).not.toHaveBeenCalled(); + + $httpBackend.flush(); + + var response = callback.mostRecentCall.args[0]; + + expect(response).toEqualData({ + data: {id: 123, number: '9876'}, + status: 200, + config: {method: 'GET', data: undefined, url: '/CreditCard/123'}, + resource: {id: 123, number: '9876', $resolved: true} + }); + expect(typeof response.resource.$save).toBe('function'); + }); + + + it('should keep $then around after promise resolution', function() { + $httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'}); + var cc = CreditCard.get({id: 123}); + + cc.$then(callback); + $httpBackend.flush(); + + var response = callback.mostRecentCall.args[0]; + + callback.reset(); + + cc.$then(callback); + $rootScope.$apply(); //flush async queue + + expect(callback).toHaveBeenCalledOnceWith(response); + }); + + + it('should allow promise chaining via $then method', function() { + $httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'}); + var cc = CreditCard.get({id: 123}); + + cc.$then(function(response) { return 'new value'; }).then(callback); + $httpBackend.flush(); + + expect(callback).toHaveBeenCalledOnceWith('new value'); + }); + + + it('should allow error callback registration via $then method', function() { + $httpBackend.expect('GET', '/CreditCard/123').respond(404, 'resource not found'); + var cc = CreditCard.get({id: 123}); + + cc.$then(null, callback); + $httpBackend.flush(); + + var response = callback.mostRecentCall.args[0]; + + expect(response).toEqualData({ + data : 'resource not found', + status : 404, + config : { method : 'GET', data : undefined, url : '/CreditCard/123' } + }); + }); + + + it('should add $resolved boolean field to the result object', function() { + $httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'}); + var cc = CreditCard.get({id: 123}); + + expect(cc.$resolved).toBe(false); + + cc.$then(callback); + expect(cc.$resolved).toBe(false); + + $httpBackend.flush(); + + expect(cc.$resolved).toBe(true); + }); + + + it('should set $resolved field to true when an error occurs', function() { + $httpBackend.expect('GET', '/CreditCard/123').respond(404, 'resource not found'); + var cc = CreditCard.get({id: 123}); + + cc.$then(null, callback); + $httpBackend.flush(); + expect(callback).toHaveBeenCalledOnce(); + expect(cc.$resolved).toBe(true); + }); + }); + + + describe('resource collection', function() { + + it('should add promise $then method to the result object', function() { + $httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]); + var ccs = CreditCard.query({key: 'value'}); + + ccs.$then(callback); + expect(callback).not.toHaveBeenCalled(); + + $httpBackend.flush(); + + var response = callback.mostRecentCall.args[0]; + + expect(response).toEqualData({ + data: [{id: 1}, {id :2}], + status: 200, + config: {method: 'GET', data: undefined, url: '/CreditCard?key=value'}, + resource: [ { id : 1 }, { id : 2 } ] + }); + expect(typeof response.resource[0].$save).toBe('function'); + expect(typeof response.resource[1].$save).toBe('function'); + }); + + + it('should keep $then around after promise resolution', function() { + $httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]); + var ccs = CreditCard.query({key: 'value'}); + + ccs.$then(callback); + $httpBackend.flush(); + + var response = callback.mostRecentCall.args[0]; + + callback.reset(); + + ccs.$then(callback); + $rootScope.$apply(); //flush async queue + + expect(callback).toHaveBeenCalledOnceWith(response); + }); + + + it('should allow promise chaining via $then method', function() { + $httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]); + var ccs = CreditCard.query({key: 'value'}); + + ccs.$then(function(response) { return 'new value'; }).then(callback); + $httpBackend.flush(); + + expect(callback).toHaveBeenCalledOnceWith('new value'); + }); + + + it('should allow error callback registration via $then method', function() { + $httpBackend.expect('GET', '/CreditCard?key=value').respond(404, 'resource not found'); + var ccs = CreditCard.query({key: 'value'}); + + ccs.$then(null, callback); + $httpBackend.flush(); + + var response = callback.mostRecentCall.args[0]; + + expect(response).toEqualData({ + data : 'resource not found', + status : 404, + config : { method : 'GET', data : undefined, url : '/CreditCard?key=value' } + }); + }); + + + it('should add $resolved boolean field to the result object', function() { + $httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]); + var ccs = CreditCard.query({key: 'value'}, callback); + + expect(ccs.$resolved).toBe(false); + + ccs.$then(callback); + expect(ccs.$resolved).toBe(false); + + $httpBackend.flush(); + + expect(ccs.$resolved).toBe(true); + }); + + + it('should set $resolved field to true when an error occurs', function() { + $httpBackend.expect('GET', '/CreditCard?key=value').respond(404, 'resource not found'); + var ccs = CreditCard.query({key: 'value'}); + + ccs.$then(null, callback); + $httpBackend.flush(); + expect(callback).toHaveBeenCalledOnce(); + expect(ccs.$resolved).toBe(true); + }); + }); }); |
