@@ -63,6 +63,8 @@ function shallowClearAndCopy(src, dst) {
6363 * @ngdoc service
6464 * @name $resource
6565 * @requires $http
66+ * @requires ng.$log
67+ * @requires $q
6668 *
6769 * @description
6870 * A factory which creates a resource object that lets you interact with
@@ -107,9 +109,9 @@ function shallowClearAndCopy(src, dst) {
107109 * URL `/path/greet?salutation=Hello`.
108110 *
109111 * If the parameter value is prefixed with `@` then the value for that parameter will be extracted
110- * from the corresponding property on the `data` object (provided when calling an action method). For
111- * example, if the `defaultParam` object is `{someParam: '@someProp '}` then the value of `someParam`
112- * will be `data.someProp`.
112+ * from the corresponding property on the `data` object (provided when calling an action method).
113+ * For example, if the `defaultParam` object is `{someParam: '@someProp '}` then the value of
114+ * `someParam` will be `data.someProp`.
113115 *
114116 * @param {Object.<Object>= } actions Hash with declaration of custom actions that should extend
115117 * the default set of resource actions. The declaration should be created in the format of {@link
@@ -143,15 +145,23 @@ function shallowClearAndCopy(src, dst) {
143145 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
144146 * transform function or an array of such functions. The transform function takes the http
145147 * response body and headers and returns its transformed (typically deserialized) version.
146- * By default, transformResponse will contain one function that checks if the response looks like
147- * a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, set
148- * `transformResponse` to an empty array: `transformResponse: []`
148+ * By default, transformResponse will contain one function that checks if the response looks
149+ * like a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior,
150+ * set `transformResponse` to an empty array: `transformResponse: []`
149151 * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
150152 * GET request, otherwise if a cache instance built with
151153 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
152154 * caching.
153- * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that
154- * should abort the request when resolved.
155+ * - **`timeout`** – `{number}` – timeout in milliseconds.<br />
156+ * **Note:** In contrast to {@link ng.$http#usage $http.config}, {@link ng.$q promises} are
157+ * **not** supported in $resource, because the same value has to be re-used for multiple
158+ * requests. If you are looking for a way to cancel requests, you should use the `cancellable`
159+ * option.
160+ * - **`cancellable`** – `{boolean}` – if set to true, the request made by a "non-instance" call
161+ * will be cancelled (if not already completed) by calling `$cancelRequest()` on the call's
162+ * return value. Calling `$cancelRequest()` for a non-cancellable or an already
163+ * completed/cancelled request will have no effect.<br />
164+ * **Note:** If a timeout is specified in millisecondes, `cancellable` is ignored.
155165 * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
156166 * XHR object. See
157167 * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5)
@@ -163,12 +173,13 @@ function shallowClearAndCopy(src, dst) {
163173 * with `http response` object. See {@link ng.$http $http interceptors}.
164174 *
165175 * @param {Object } options Hash with custom settings that should extend the
166- * default `$resourceProvider` behavior. The only supported option is
167- *
168- * Where:
176+ * default `$resourceProvider` behavior. The supported options are:
169177 *
170178 * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing
171179 * slashes from any calculated URL will be stripped. (Defaults to true.)
180+ * - **`cancellable`** – {boolean} – If true, the request made by a "non-instance" call will be
181+ * cancelled (if not already completed) by calling `$cancelRequest()` on the call's return value.
182+ * This can be overwritten per action. (Defaults to false.)
172183 *
173184 * @returns {Object } A resource "class" object with methods for the default set of resource actions
174185 * optionally extended with custom `actions`. The default set contains these actions:
@@ -216,7 +227,7 @@ function shallowClearAndCopy(src, dst) {
216227 * Class actions return empty instance (with additional properties below).
217228 * Instance actions return promise of the action.
218229 *
219- * The Resource instances and collection have these additional properties:
230+ * The Resource instances and collections have these additional properties:
220231 *
221232 * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
222233 * instance or collection.
@@ -236,6 +247,11 @@ function shallowClearAndCopy(src, dst) {
236247 * rejection), `false` before that. Knowing if the Resource has been resolved is useful in
237248 * data-binding.
238249 *
250+ * The Resource instances and collections have these additional methods:
251+ *
252+ * - `$cancelRequest`: If there is a cancellable, pending request related to the instance or
253+ * collection, calling this method will abort the request.
254+ *
239255 * @example
240256 *
241257 * # Credit card resource
@@ -280,6 +296,11 @@ function shallowClearAndCopy(src, dst) {
280296 *
281297 * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
282298 * `headers`.
299+ *
300+ * @example
301+ *
302+ * # User resource
303+ *
283304 * When the data is returned from the server then the object is an instance of the resource type and
284305 * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
285306 * operations (create, read, update, delete) on server-side data.
@@ -298,10 +319,10 @@ function shallowClearAndCopy(src, dst) {
298319 *
299320 ```js
300321 var User = $resource('/user/:userId', {userId:'@id '});
301- User.get({userId:123}, function(u , getResponseHeaders){
302- u .abc = true;
303- u .$save(function(u , putResponseHeaders) {
304- //u => saved user object
322+ User.get({userId:123}, function(user , getResponseHeaders){
323+ user .abc = true;
324+ user .$save(function(user , putResponseHeaders) {
325+ //user => saved user object
305326 //putResponseHeaders => $http header getter
306327 });
307328 });
@@ -316,8 +337,11 @@ function shallowClearAndCopy(src, dst) {
316337 $scope.user = user;
317338 });
318339 ```
319-
340+ *
341+ * @example
342+ *
320343 * # Creating a custom 'PUT' request
344+ *
321345 * In this example we create a custom method on our resource to make a PUT request
322346 * ```js
323347 * var app = angular.module('app', ['ngResource', 'ngRoute']);
@@ -345,6 +369,34 @@ function shallowClearAndCopy(src, dst) {
345369 * // This will PUT /notes/ID with the note object in the request payload
346370 * }]);
347371 * ```
372+ *
373+ * @example
374+ *
375+ * # Cancelling requests
376+ *
377+ * If an action's configuration specifies that it is cancellable, you can cancel the request related
378+ * to an instance or collection (as long as it is a result of a "non-instance" call):
379+ *
380+ ```js
381+ // ...defining the `Hotel` resource...
382+ var Hotel = $resource('/api/hotel/:id', {id: '@id' }, {
383+ // Let's make the `query()` method cancellable
384+ query: {method: 'get', isArray: true, cancellable: true}
385+ });
386+
387+ // ...somewhere in the PlanVacationController...
388+ ...
389+ this.onDestinationChanged = function onDestinationChanged(destination) {
390+ // We don't care about any pending request for hotels
391+ // in a different destination any more
392+ this.availableHotels.$cancelRequest();
393+
394+ // Let's query for hotels in '<destination>'
395+ // (calls: /api/hotel?location=<destination>)
396+ this.availableHotels = Hotel.query({location: destination});
397+ };
398+ ```
399+ *
348400 */
349401angular . module ( 'ngResource' , [ 'ng' ] ) .
350402 provider ( '$resource' , function ( ) {
@@ -365,7 +417,7 @@ angular.module('ngResource', ['ng']).
365417 }
366418 } ;
367419
368- this . $get = [ '$http' , '$q' , function ( $http , $q ) {
420+ this . $get = [ '$http' , '$log' , '$ q', function ( $http , $log , $q ) {
369421
370422 var noop = angular . noop ,
371423 forEach = angular . forEach ,
@@ -524,6 +576,22 @@ angular.module('ngResource', ['ng']).
524576
525577 forEach ( actions , function ( action , name ) {
526578 var hasBody = / ^ ( P O S T | P U T | P A T C H ) $ / i. test ( action . method ) ;
579+ var cancellable ;
580+
581+ if ( angular . isNumber ( action . timeout ) ) {
582+ cancellable = false ;
583+ } else if ( action . timeout ) {
584+ $log . debug ( 'ngResource:\n' +
585+ ' Only numeric values are allowed as `timeout`.\n' +
586+ ' Promises are not supported in $resource, because the same value has to ' +
587+ 'be re-used for multiple requests. If you are looking for a way to cancel ' +
588+ 'requests, you should use the `cancellable` option.' ) ;
589+ delete action . timeout ;
590+ } else {
591+ cancellable = angular . isDefined ( action . cancellable ) ? action . cancellable :
592+ ( options && angular . isDefined ( options . cancellable ) ) ? options . cancellable :
593+ provider . defaults . cancellable ;
594+ }
527595
528596 Resource [ name ] = function ( a1 , a2 , a3 , a4 ) {
529597 var params = { } , data , success , error ;
@@ -572,6 +640,7 @@ angular.module('ngResource', ['ng']).
572640 defaultResponseInterceptor ;
573641 var responseErrorInterceptor = action . interceptor && action . interceptor . responseError ||
574642 undefined ;
643+ var timeoutDeferred ;
575644
576645 forEach ( action , function ( value , key ) {
577646 switch ( key ) {
@@ -581,21 +650,23 @@ angular.module('ngResource', ['ng']).
581650 case 'params' :
582651 case 'isArray' :
583652 case 'interceptor' :
584- break ;
585- case 'timeout' :
586- httpConfig [ key ] = value ;
653+ case 'cancellable' :
587654 break ;
588655 }
589656 } ) ;
590657
658+ if ( ! isInstanceCall && cancellable ) {
659+ timeoutDeferred = $q . defer ( ) ;
660+ httpConfig . timeout = timeoutDeferred . promise ;
661+ }
662+
591663 if ( hasBody ) httpConfig . data = data ;
592664 route . setUrlParams ( httpConfig ,
593665 extend ( { } , extractParams ( data , action . params || { } ) , params ) ,
594666 action . url ) ;
595667
596668 var promise = $http ( httpConfig ) . then ( function ( response ) {
597- var data = response . data ,
598- promise = value . $promise ;
669+ var data = response . data ;
599670
600671 if ( data ) {
601672 // Need to convert action.isArray to boolean in case it is undefined
@@ -620,24 +691,27 @@ angular.module('ngResource', ['ng']).
620691 }
621692 } ) ;
622693 } else {
694+ var promise = value . $promise ; // Save the promise
623695 shallowClearAndCopy ( data , value ) ;
624- value . $promise = promise ;
696+ value . $promise = promise ; // Restore the promise
625697 }
626698 }
627-
628- value . $resolved = true ;
629-
630699 response . resource = value ;
631700
632701 return response ;
633702 } , function ( response ) {
634- value . $resolved = true ;
635-
636703 ( error || noop ) ( response ) ;
637-
638704 return $q . reject ( response ) ;
639705 } ) ;
640706
707+ promise . finally ( function ( ) {
708+ value . $resolved = true ;
709+ if ( cancellable ) {
710+ value . $cancelRequest = angular . noop ;
711+ timeoutDeferred = httpConfig . timeout = null ;
712+ }
713+ } ) ;
714+
641715 promise = promise . then (
642716 function ( response ) {
643717 var value = responseInterceptor ( response ) ;
@@ -652,6 +726,7 @@ angular.module('ngResource', ['ng']).
652726 // - return the instance / collection
653727 value . $promise = promise ;
654728 value . $resolved = false ;
729+ if ( cancellable ) value . $cancelRequest = timeoutDeferred . resolve ;
655730
656731 return value ;
657732 }
0 commit comments