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

Can no longer cancel $resource request with a promise #9332

Closed
simonjm opened this issue Sep 29, 2014 · 20 comments
Closed

Can no longer cancel $resource request with a promise #9332

simonjm opened this issue Sep 29, 2014 · 20 comments

Comments

@simonjm
Copy link

simonjm commented Sep 29, 2014

I've tested this on Chrome 37.0.2062.124 m, Firefox 32.0.3, and IE11 on Windows 8.1

Here is some code to reproduce the issue.

<body ng-app="testApp">
    <div ng-controller="TestCtrl">
        <button type="button" ng-click="clear()">Clear</button>
        <ul>
            <li ng-repeat="val in dataset">
                {{val | json}}
            </li>
        </ul>
        <button type="button" ng-click="start()">Start</button>
        <button type="button" ng-click="stop()">Stop</button>
    </div>
    <script type="text/javascript" src="https://code.angularjs.org/1.3.0-rc.3/angular.js"></script>
    <script type="text/javascript" src="https://code.angularjs.org/1.3.0-rc.3/angular-resource.js"></script>
    <script type="text/javascript">
        angular.module('testApp', ['ngResource']).controller('TestCtrl', function ($scope, $resource, $q) {                                   
            var promises = [];
            function getResource() {
                var defered = $q.defer();
                promises.push(defered);
                return $resource('/data', {}, {
                    query: {
                        method: 'GET',
                        isArray: true,
                        timeout: defered.promise
                    }
                });
            }

            $scope.start = function () {
                getResource().query(function (res) {
                    $scope.dataset = res;
                });
            };

            $scope.stop = function () {
                promises.forEach(function (p) { p.resolve(); });
                promises = [];
            };

            $scope.clear = function() {
                $scope.dataset = [];
            }
        });
    </script>
</body>

23bc92b appears to be the culprit. Before this the above code works as expected.

@caitp
Copy link
Contributor

caitp commented Sep 29, 2014

there are tests in the tree which assert that this does work, so it seems something else is wrong with your code. I'm not sure what at the moment, however

@caitp
Copy link
Contributor

caitp commented Sep 29, 2014

I think the main problem with this is that you end up trying to resolve your promise more than once, but I'm not really positive here

@caitp
Copy link
Contributor

caitp commented Sep 29, 2014

@lgalfaso does this seem like a bug to you? on the one hand the API doesn't seem super useful of the promise can only be resolved once --- on the other hand, it doesn't seem to be working at all, so I must be missing something about why this is broken

@joshkurz
Copy link
Contributor

yeah I noticed this recently as well. I altered the same code to use $http
and all was well.

I did a quick search and did not see a $resource test that actually uses
{timeout: promise} as a config value, though so maybe we are not testing
for it properly.

On Mon, Sep 29, 2014 at 6:53 PM, Caitlin Potter notifications@github.com
wrote:

@lgalfaso https://github.com/lgalfaso does this seem like a bug to you?
on the one hand the API doesn't seem super useful of the promise can only
be resolved once --- on the other hand, it doesn't seem to be working at
all, so I must be missing something about why this is broken


Reply to this email directly or view it on GitHub
#9332 (comment).

Josh Kurz
www.joshkurz.net

@caitp
Copy link
Contributor

caitp commented Sep 29, 2014

there is no resource test for this, but I think it's weird to use this for resource, because you're configuring it once, and then that promise is basically permanently cancelled if it ever gets cancelled --- which isn't super useful

@joshkurz
Copy link
Contributor

true, but you could just create another one if you wanted too. I think
canceling requests is very useful. I use it all the time. If a request is
made, while another is going on, I just resolve and create a new promise
object to use as the timeout value. This works really well.

Your thinking about passing the {timeout: promise} another way, other than
request's config?

On Mon, Sep 29, 2014 at 7:19 PM, Caitlin Potter notifications@github.com
wrote:

there is no resource test for this, but I think it's weird to use this for
resource, because you're configuring it once, and then that promise is
basically permanently cancelled if it ever gets cancelled --- which isn't
super useful


Reply to this email directly or view it on GitHub
#9332 (comment).

Josh Kurz
www.joshkurz.net

@caitp
Copy link
Contributor

caitp commented Sep 29, 2014

well, you'd have to give $resource a factory for creating a cancelled promise, and $resource would have to expose a way to resolve that factory-created promise, otherwise you'd have to rely on hacks to make that work.

I think if we want to have an API like this, it needs to be reworked, because this is crazy.

But again, I'm still not even sure that this is actually the problem they're having, it's very hard to debug this.

@jeffbcross jeffbcross modified the milestones: Backlog, 1.3.0 Sep 29, 2014
@simonjm
Copy link
Author

simonjm commented Sep 30, 2014

Maybe this is a better example of the problem. If I update the $scope.start function to this

            $scope.start = function () {
                var defered = $q.defer();
                $timeout(function() { defered.resolve(); }, 2);
                $http.get('/data', {timeout : defered.promise}).success(function(res) {
                    $scope.dataset = res;
                });
            };

The request is cancelled as expected. Now if I update $scope.start to use $resource

            $scope.start = function () {
                var defered = $q.defer();
                $timeout(function() { defered.resolve(); }, 2);
                $resource('/data', {}, {
                    query : {
                        method : 'GET',
                        isArray : true,
                        timeout : defered.promise
                    }
                }).query(function(res) {
                    $scope.dataset = res;
                });
            };

The request doesn't get cancelled.

@caitp
Copy link
Contributor

caitp commented Sep 30, 2014

None of this is really helpful in debugging this, and I can't think of a reason why this would have worked before --- unless we previously did let you resolve promises more than once, and I'm pretty sure we didn't. I am not seeing what changed, but I am seeing that this API didn't make any sense before, and it definitely needs some changes to be really useful to people. Unfortunately, even with the changes I'm thinking of, you'd only ever be able to cancel the most recent request that was made, which might not be what people want to do. So it's really hard to make this work "well"

@guilbep
Copy link
Contributor

guilbep commented Oct 16, 2014

@caitp Is it wanted behavior that only defaults.transformRequest defaults.transformResponse and default.withCredentials are passed to the config in function https://github.com/angular/angular.js/blob/master/src/ng/http.js#L706 (sorry for using lines in links) .. just adding timeout: defaults.timeout will do.

@caitp
Copy link
Contributor

caitp commented Oct 16, 2014

well again, defaults.timeout would only actually work right if it was a number (which is what you'd expect from a timeout variable) --- but if it were a promise, this would never work right. So I guess yeah that's probably the main reason why we don't include it.

@guilbep
Copy link
Contributor

guilbep commented Oct 16, 2014

@caitp That's not nice. Maybe we should add a section into the documentation that says so. I could maybe do it if you think that's right. https://docs.angularjs.org/api/ng/provider/$httpProvider ?

@tsonev
Copy link

tsonev commented Jan 6, 2015

This is a very valuable article, around which I have created my own duplicate request aborter service.

https://developer.rackspace.com/blog/cancelling-ajax-requests-in-angularjs-applications/

@caitp: Does it mean that from 1.3 on this will not be possible anymore?

@valente
Copy link

valente commented Apr 23, 2015

I tracked down this issue to resource.js, 563:

            forEach(action, function(value, key) {
              if (key != 'params' && key != 'isArray' && key != 'interceptor') {
                httpConfig[key] = copy(value);
              }
            });

The function clones all config values except some with special treatment. The timeout promise is currently getting cloned. So, when httpBackend.js, 115 receives the timeout,

    } else if (isPromiseLike(timeout)) {
      timeout.then(timeoutRequest);
    }

It actually registers that callback on a clone, which never gets resolved.

I made a quick & dirty fix which doesn't clone but just assigns a 'timeout' property in the iteration above, and issue gets resolved.

@qpitlove
Copy link

qpitlove commented Jun 8, 2015

👍 @valente

@psergus
Copy link

psergus commented Aug 28, 2015

any workaround for this?

@kayhadrin
Copy link

I've crafted a promise that can be cancelled so that I can ignore the ajax
request result if need be.

On Sat, 29 Aug 2015 at 09:46 Sergey Petrenko notifications@github.com
wrote:

any workaround for this?


Reply to this email directly or view it on GitHub
#9332 (comment)
.

@psergus
Copy link

psergus commented Aug 29, 2015

yeah, I was playing with the promise as well and following this example https://developer.rackspace.com/blog/cancelling-ajax-requests-in-angularjs-applications/ I can do just abort() on resource and ignore the ajax response. However, it does not solve the issue. If a browser is clogged if slow responses, which cannot be cancelled, it decreases the performance level.
I am contemplating to write my own lightweight version of ngResource with cancelable actions.

@kayhadrin
Copy link

Correct me if I'm wrong, but I thought that applying a patch for this was
relatively easy in Angular's source code?

On Sat, 29 Aug 2015 at 13:34 Sergey Petrenko notifications@github.com
wrote:

yeah, I was playing with the promise as well and following this example
https://developer.rackspace.com/blog/cancelling-ajax-requests-in-angularjs-applications/
I can do just abort() on resource and ignore the ajax response. However, it
does not solve the issue. If a browser is clogged if slow responses, which
cannot be cancelled, it decreases the performance level.
I am contemplating to write my own lightweight version of ngResource with
cancelable actions.


Reply to this email directly or view it on GitHub
#9332 (comment)
.

@darabos
Copy link

darabos commented Oct 8, 2015

I see the reason behind copying the config from the Resource to the $http request. (Isolating requests from each other.) But cancellation really needs to be solved. How can you live without it?

I see the arguments for the Resource actions are already Byzantine, but could we add one more optional argument that is a per-request config object for $http? This could be merged with the Resource-level config without copying.

This could be useful in other cases as well. (E.g. decide caching on a per-request basis.) I'd be happy to work on this issue with a little guidance. Thanks!

gkalpak added a commit to gkalpak/angular.js that referenced this issue Oct 9, 2015
Previously, it was not possible to use a promise as value for the
`timeout` property of an actions `config`, because that value would be
copied before being passed to `$http`.
This commit introduces a special value for the `timeout`, namely
`'promise'`. Setting action's `timeout` configuration property to
`'promise'`, will create a new promise and pass it to `$http` for each
request. The associated deferred, is attached to the resource instance,
under `$timeoutDeferred`.

Example usage:

```js
var Post = $resource('/posts/:id', {id: '@id'}, {
  query: {
    method: 'GET',
    isArray: true,
    timeout: 'promise'
  }
});

var posts = Post.query();   // GET /posts
...
// Later we decide to cancel the request
// in order to make a new one
posts.$timeoutDeferred.resolve();
posts.query({author: 'me'});   // GET /posts?author=me
...
```

Fixes angular#9332
Closes angular#13050
gkalpak added a commit to gkalpak/angular.js that referenced this issue Oct 9, 2015
Previously, it was not possible to use a promise as value for the
`timeout` property of an action's `config`, because that value would be
copied before being passed to `$http`.
This commit introduces a special value for the `timeout`, namely
`'promise'`. Setting an action's `timeout` configuration property to
`'promise'`, will create a new promise and pass it to `$http` for each
request. The associated deferred, is attached to the resource instance,
under `$timeoutDeferred`.

Example usage:

```js
var Post = $resource('/posts/:id', {id: '@id'}, {
  query: {
    method: 'GET',
    isArray: true,
    timeout: 'promise'
  }
});

var posts = Post.query();   // GET /posts
...
// Later we decide to cancel the request
// in order to make a new one
posts.$timeoutDeferred.resolve();
posts.query({author: 'me'});   // GET /posts?author=me
...
```

Fixes angular#9332
Closes angular#13050
gkalpak added a commit to gkalpak/angular.js that referenced this issue Oct 30, 2015
Introduced changes:

- Deprecate passing a promise as `timeout` (for `$resource` actions).
  It never worked correctly anyway.
  Now a warnign is logged (using `$log.debug()`) and the property is
  removed.
- Add support for a boolean `cancellable` property to actions'
  configuration, the `$resource` classes `options` of the
  `$resourceProvider`'s defaults.
  If true, the `$cancelRequest` method (added to all returned values for
  non-instance calls) will abort the request (if it's not already
  completed or aborted).
  If there is `timeout` specified on the action's configuration, the value
  of `cancellable` is ignored.

Example usage:

```js
var Post = $resource('/posts/:id', {id: '@id'}, {
  get: {
    method: 'GET',
    cancellable: true
  }
});

var currentPost = Post.get({id: 1});
...
// A moment later the user selects another post, so
// we don't need the previous request any more
currentPost.$cancelRequest();
currentPost = Post.get({id: 2});
...
```

Fixes angular#9332
Closes angular#13050
Closes angular#13058
@petebacondarwin petebacondarwin modified the milestones: 1.5.x - migration-facilitation, Backlog Nov 1, 2015
gkalpak added a commit to gkalpak/angular.js that referenced this issue Nov 23, 2015
Introduced changes:

- Deprecate passing a promise as `timeout` (for `$resource` actions).
  It never worked correctly anyway.
  Now a warnign is logged (using `$log.debug()`) and the property is
  removed.
- Add support for a boolean `cancellable` property to actions'
  configuration, the `$resource` classes `options` of the
  `$resourceProvider`'s defaults.
  If true, the `$cancelRequest` method (added to all returned values for
  non-instance calls) will abort the request (if it's not already
  completed or aborted).
  If there is `timeout` specified on the action's configuration, the value
  of `cancellable` is ignored.

Example usage:

```js
var Post = $resource('/posts/:id', {id: '@id'}, {
  get: {
    method: 'GET',
    cancellable: true
  }
});

var currentPost = Post.get({id: 1});
...
// A moment later the user selects another post, so
// we don't need the previous request any more
currentPost.$cancelRequest();
currentPost = Post.get({id: 2});
...
```

Fixes angular#9332
Closes angular#13050
Closes angular#13058
gkalpak added a commit to gkalpak/angular.js that referenced this issue Nov 23, 2015
Introduced changes:

- Deprecate passing a promise as `timeout` (for `$resource` actions).
  It never worked correctly anyway.
  Now a warning is logged (using `$log.debug()`) and the property is
  removed.
- Add support for a boolean `cancellable` property in actions'
  configuration, the `$resource` factory's `options` parameter and the
  `$resourceProvider`'s `defaults` property.
  If true, the `$cancelRequest` method (added to all returned values for
  non-instance calls) will abort the request (if it's not already
  completed or aborted).
  If there is a numeric `timeout` specified on the action's configuration,
  the value of `cancellable` will be ignored.

Example usage:

```js
var Post = $resource('/posts/:id', {id: '@id'}, {
  get: {
    method: 'GET',
    cancellable: true
  }
});

var currentPost = Post.get({id: 1});
...
// A moment later the user selects another post, so
// we don't need the previous request any more
currentPost.$cancelRequest();
currentPost = Post.get({id: 2});
...
```

BREAKING CHANGE:

Using a promise as `timeout` is no longer supported and will log a
warning. It never worked the way it was supposed to anyway.

Before:

```js
var deferred = $q.defer();
var User = $resource('/api/user/:id', {id: '@id'}, {
  get: {method: 'GET', timeout: deferred.promise}
});

var user = User.get({id: 1});   // sends a request
deferred.resolve();             // aborts the request

// Now, we need to re-define `User` passing a new promise as `timeout`
// or else all subsequent requests from `someAction` will be aborted
User = $resource(...);
user = User.get({id: 2});
```

After:

```js
var User = $resource('/api/user/:id', {id: '@id'}, {
  get: {method: 'GET', cancellable: true}
});

var user = User.get({id: 1});   // sends a request
instance.$cancelRequest();      // aborts the request

user = User.get({id: 2});
```

Fixes angular#9332
Closes angular#13050
Closes angular#13058
petebacondarwin pushed a commit to petebacondarwin/angular.js that referenced this issue Nov 23, 2015
Introduced changes:

- Deprecate passing a promise as `timeout` (for `$resource` actions).
  It never worked correctly anyway.
  Now a warning is logged (using `$log.debug()`) and the property is
  removed.
- Add support for a boolean `cancellable` property in actions'
  configuration, the `$resource` factory's `options` parameter and the
  `$resourceProvider`'s `defaults` property.
  If true, the `$cancelRequest` method (added to all returned values for
  non-instance calls) will abort the request (if it's not already
  completed or aborted).
  If there is a numeric `timeout` specified on the action's configuration,
  the value of `cancellable` will be ignored.

Example usage:

```js
var Post = $resource('/posts/:id', {id: '@id'}, {
  get: {
    method: 'GET',
    cancellable: true
  }
});

var currentPost = Post.get({id: 1});
...
// A moment later the user selects another post, so
// we don't need the previous request any more
currentPost.$cancelRequest();
currentPost = Post.get({id: 2});
...
```

BREAKING CHANGE:

Using a promise as `timeout` is no longer supported and will log a
warning. It never worked the way it was supposed to anyway.

Before:

```js
var deferred = $q.defer();
var User = $resource('/api/user/:id', {id: '@id'}, {
  get: {method: 'GET', timeout: deferred.promise}
});

var user = User.get({id: 1});   // sends a request
deferred.resolve();             // aborts the request

// Now, we need to re-define `User` passing a new promise as `timeout`
// or else all subsequent requests from `someAction` will be aborted
User = $resource(...);
user = User.get({id: 2});
```

After:

```js
var User = $resource('/api/user/:id', {id: '@id'}, {
  get: {method: 'GET', cancellable: true}
});

var user = User.get({id: 1});   // sends a request
instance.$cancelRequest();      // aborts the request

user = User.get({id: 2});
```

Fixes angular#9332
Closes angular#13050
Closes angular#13058
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Nov 23, 2015
Introduced changes:

- Deprecate passing a promise as `timeout` (for `$resource` actions).
  It never worked correctly anyway.
  Now a warning is logged (using `$log.debug()`) and the property is
  removed.
- Provide a `cancelRequest` static method on the Resource that will abort
  the request (if it's not already completed or aborted).
  If there is a numeric `timeout` specified on the action's configuration,
  this method will have no effect.

Example usage:

```js
var Post = $resource('/posts/:id', {id: '@id'}, {
  get: {
    method: 'GET'
  }
});

var currentPost = Post.get({id: 1});
...
// A moment later the user selects another post, so
// we don't need the previous request any more
Post.cancelRequest(currentPost);
currentPost = Post.get({id: 2});
...
```

BREAKING CHANGE:

Using a promise as `timeout` is no longer supported and will log a
warning. It never worked the way it was supposed to anyway.

Before:

```js
var deferred = $q.defer();
var User = $resource('/api/user/:id', {id: '@id'}, {
  get: {method: 'GET', timeout: deferred.promise}
});

var user = User.get({id: 1});   // sends a request
deferred.resolve();             // aborts the request

// Now, we need to re-define `User` passing a new promise as `timeout`
// or else all subsequent requests from `someAction` will be aborted
User = $resource(...);
user = User.get({id: 2});
```

After:

```js
var User = $resource('/api/user/:id', {id: '@id'}, {
  get: {method: 'GET'}
});

var user = User.get({id: 1});   // sends a request
User.cancelRequest(instance);   // aborts the request

user = User.get({id: 2});
```

Fixes angular#9332
Closes angular#13050
Closes angular#13058
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Nov 23, 2015
Introduced changes:

- Deprecate passing a promise as `timeout` (for `$resource` actions).
  It never worked correctly anyway.
  Now a warning is logged (using `$log.debug()`) and the property is
  removed.
- Provide a `cancelRequest` static method on the Resource that will abort
  the request (if it's not already completed or aborted).
  If there is a numeric `timeout` specified on the action's configuration,
  this method will have no effect.

Example usage:

```js
var Post = $resource('/posts/:id', {id: '@id'}, {
  get: {
    method: 'GET'
  }
});

var currentPost = Post.get({id: 1});
...
// A moment later the user selects another post, so
// we don't need the previous request any more
Post.cancelRequest(currentPost);
currentPost = Post.get({id: 2});
...
```

BREAKING CHANGE:

Using a promise as `timeout` is no longer supported and will log a
warning. It never worked the way it was supposed to anyway.

Before:

```js
var deferred = $q.defer();
var User = $resource('/api/user/:id', {id: '@id'}, {
  get: {method: 'GET', timeout: deferred.promise}
});

var user = User.get({id: 1});   // sends a request
deferred.resolve();             // aborts the request

// Now, we need to re-define `User` passing a new promise as `timeout`
// or else all subsequent requests from `someAction` will be aborted
User = $resource(...);
user = User.get({id: 2});
```

After:

```js
var User = $resource('/api/user/:id', {id: '@id'}, {
  get: {method: 'GET'}
});

var user = User.get({id: 1});   // sends a request
User.cancelRequest(instance);   // aborts the request

user = User.get({id: 2});
```

Fixes angular#9332
Closes angular#13050
Closes angular#13058
gkalpak added a commit that referenced this issue Nov 24, 2015
Introduced changes:

- Deprecate passing a promise as `timeout` (for `$resource` actions).
  It never worked correctly anyway.
  Now a warning is logged (using `$log.debug()`) and the property is
  removed.
- Add support for a boolean `cancellable` property in actions'
  configuration, the `$resource` factory's `options` parameter and the
  `$resourceProvider`'s `defaults` property.
  If true, the `$cancelRequest` method (added to all returned values for
  non-instance calls) will abort the request (if it's not already
  completed or aborted).
  If there is a numeric `timeout` specified on the action's configuration,
  the value of `cancellable` will be ignored.

Example usage:

```js
var Post = $resource('/posts/:id', {id: '@id'}, {
  get: {
    method: 'GET',
    cancellable: true
  }
});

var currentPost = Post.get({id: 1});
...
// A moment later the user selects another post, so
// we don't need the previous request any more
currentPost.$cancelRequest();
currentPost = Post.get({id: 2});
...
```

BREAKING CHANGE:

Using a promise as `timeout` is no longer supported and will log a
warning. It never worked the way it was supposed to anyway.

Before:

```js
var deferred = $q.defer();
var User = $resource('/api/user/:id', {id: '@id'}, {
  get: {method: 'GET', timeout: deferred.promise}
});

var user = User.get({id: 1});   // sends a request
deferred.resolve();             // aborts the request

// Now, we need to re-define `User` passing a new promise as `timeout`
// or else all subsequent requests from `someAction` will be aborted
User = $resource(...);
user = User.get({id: 2});
```

After:

```js
var User = $resource('/api/user/:id', {id: '@id'}, {
  get: {method: 'GET', cancellable: true}
});

var user = User.get({id: 1});   // sends a request
instance.$cancelRequest();      // aborts the request

user = User.get({id: 2});
```

Fixes #9332
Closes #13050
Closes #13058
Closes #13210
kkirsche added a commit to kkirsche/kibana that referenced this issue Feb 21, 2016
<a name="1.4.9"></a>
# 1.4.9 implicit-superannuation (2016-01-21)

## Bug Fixes

- **Animation**
  - ensure that animate promises resolve when the document is hidden
  ([9a60408c](angular/angular.js@9a60408))
  - do not trigger animations if the document is hidden
  ([09f6061a](angular/angular.js@09f6061),
   [elastic#12842](angular/angular.js#12842), [elastic#13776](angular/angular.js#13776))
  - only copy over the animation options once
  ([2fc954d3](angular/angular.js@2fc954d),
   [elastic#13722](angular/angular.js#13722), [elastic#13578](angular/angular.js#13578))
  - allow event listeners on document in IE
  ([5ba4419e](angular/angular.js@5ba4419),
   [elastic#13548](angular/angular.js#13548), [elastic#13696](angular/angular.js#13696))
  - allow removing classes that are added by a running animation
  ([6c4581fc](angular/angular.js@6c4581f),
   [elastic#13339](angular/angular.js#13339), [elastic#13380](angular/angular.js#13380), [elastic#13414](angular/angular.js#13414), [elastic#13472](angular/angular.js#13472), [elastic#13678](angular/angular.js#13678))
  - do not use `event.timeStamp` anymore for time tracking
  ([620a20d1](angular/angular.js@620a20d),
   [elastic#13494](angular/angular.js#13494), [elastic#13495](angular/angular.js#13495))
  - ignore children without animation data when closing them
  ([be01cebf](angular/angular.js@be01ceb),
   [elastic#11992](angular/angular.js#11992), [elastic#13424](angular/angular.js#13424))
  - do not alter the provided options data
  ([7a81e6fe](angular/angular.js@7a81e6f),
   [elastic#13040](angular/angular.js#13040), [elastic#13175](angular/angular.js#13175))
  - correctly handle `$animate.pin()` host elements
  ([a985adfd](angular/angular.js@a985adf),
   [elastic#13783](angular/angular.js#13783))
  - allow animations when pinned element is parent element
  ([4cb8ac61](angular/angular.js@4cb8ac6),
   [elastic#13466](angular/angular.js#13466))
  - allow enabled children to animate on disabled parents
  ([6d85f24e](angular/angular.js@6d85f24),
   [elastic#13179](angular/angular.js#13179), [elastic#13695](angular/angular.js#13695))
  - correctly access `minErr`
  ([0c1b54f0](angular/angular.js@0c1b54f))
  - ensure animate runner is the same with and without animations
  ([937942f5](angular/angular.js@937942f),
   [elastic#13205](angular/angular.js#13205), [elastic#13347](angular/angular.js#13347))
  - remove animation end event listeners on close
  ([d9157849](angular/angular.js@d915784),
   [elastic#13672](angular/angular.js#13672))
  - consider options.delay value for closing timeout
  ([592bf516](angular/angular.js@592bf51),
   [elastic#13355](angular/angular.js#13355), [elastic#13363](angular/angular.js#13363))
- **$controller:** allow identifiers containing `$`
  ([2563ff7b](angular/angular.js@2563ff7),
   [elastic#13736](angular/angular.js#13736))
- **$http:** throw if url passed is not a string
  ([c5bf9dae](angular/angular.js@c5bf9da),
   [elastic#12925](angular/angular.js#12925), [elastic#13444](angular/angular.js#13444))
- **$parse:** handle interceptors with `undefined` expressions
  ([7bb2414b](angular/angular.js@7bb2414))
- **$resource:** don't allow using promises as `timeout` and log a warning
  ([47486524](angular/angular.js@4748652))
- **formatNumber:** cope with large and small number corner cases
  ([9c49eb13](angular/angular.js@9c49eb1),
   [elastic#13394](angular/angular.js#13394), [elastic#8674](angular/angular.js#8674), [elastic#12709](angular/angular.js#12709), [elastic#8705](angular/angular.js#8705), [elastic#12707](angular/angular.js#12707), [elastic#10246](angular/angular.js#10246), [elastic#10252](angular/angular.js#10252))
- **input:**
  - fix URL validation being too strict
  ([6610ae81](angular/angular.js@6610ae8),
   [elastic#13528](angular/angular.js#13528), [elastic#13544](angular/angular.js#13544))
  - add missing chars to URL validation regex
  ([2995b54a](angular/angular.js@2995b54),
   [elastic#13379](angular/angular.js#13379), [elastic#13460](angular/angular.js#13460))
- **isArrayLike:** recognize empty instances of an Array subclass
  ([323f9ab7](angular/angular.js@323f9ab),
   [elastic#13560](angular/angular.js#13560), [elastic#13708](angular/angular.js#13708))
- **ngInclude:** do not compile template if original scope is destroyed
  ([9590bcf0](angular/angular.js@9590bcf))
- **ngOptions:**
  - don't skip `optgroup` elements with `value === ''`
  ([85e392f3](angular/angular.js@85e392f),
   [elastic#13487](angular/angular.js#13487), [elastic#13489](angular/angular.js#13489))
  - don't `$dirty` multiple select after compilation
  ([f163c905](angular/angular.js@f163c90),
   [elastic#13211](angular/angular.js#13211), [elastic#13326](angular/angular.js#13326))
- **select:** re-define `ngModelCtrl.$render` in the `select` directive's postLink function
  ([529b2507](angular/angular.js@529b250),
   [elastic#13583](angular/angular.js#13583), [elastic#13583](angular/angular.js#13583), [elastic#13663](angular/angular.js#13663))

## Minor Features

- **ngLocale:** add support for standalone months
  ([54c4041e](angular/angular.js@54c4041),
   [elastic#3744](angular/angular.js#3744), [elastic#10247](angular/angular.js#10247), [elastic#12642](angular/angular.js#12642), [elastic#12844](angular/angular.js#12844))
- **ngMock:** add support for `$animate.closeAndFlush()`
  ([512c0811](angular/angular.js@512c081))

## Performance Improvements

- **ngAnimate:** speed up `areAnimationsAllowed` check
  ([2d3303dd](angular/angular.js@2d3303d))

## Breaking Changes

While we do not deem the following to be a real breaking change we are highlighting it here in the
changelog to ensure that it does not surprise anyone.

- **$resource:** due to [47486524](angular/angular.js@4748652),

**Possible breaking change** for users who updated their code to provide a `timeout`
promise for a `$resource` request in version v1.4.8.

Up to v1.4.7 (included), using a promise as a timeout in `$resource`, would silently
fail (i.e. have no effect).

In v1.4.8, using a promise as timeout would have the (buggy) behaviour described
in angular/angular.js#12657 (comment).
(I.e. it will work as expected for the first time you resolve the promise and will
cancel all subsequent requests after that - one has to re-create the resource
class. This was not documented.)

With this change, using a promise as timeout in v1.4.9 onwards is not allowed.
It will log a warning and ignore the timeout value.

If you need support for cancellable `$resource` actions, you should upgrade to
version 1.5 or higher.

<a name="1.4.8"></a>
# 1.4.8 ice-manipulation (2015-11-19)

## Bug Fixes

- **$animate:** ensure leave animation calls `close` callback
  ([6bd6dbff](angular/angular.js@6bd6dbf),
   [elastic#12278](angular/angular.js#12278), [elastic#12096](angular/angular.js#12096), [elastic#13054](angular/angular.js#13054))
- **$cacheFactory:** check key exists before decreasing cache size count
  ([2a5a52a7](angular/angular.js@2a5a52a),
   [elastic#12321](angular/angular.js#12321), [elastic#12329](angular/angular.js#12329))
- **$compile:**
  - bind all directive controllers correctly when using `bindToController`
  ([5d8861fb](angular/angular.js@5d8861f),
   [elastic#11343](angular/angular.js#11343), [elastic#11345](angular/angular.js#11345))
  - evaluate against the correct scope with bindToController on new scope
  ([b9f7c453](angular/angular.js@b9f7c45),
   [elastic#13021](angular/angular.js#13021), [elastic#13025](angular/angular.js#13025))
  - fix scoping of transclusion directives inside replace directive
  ([74da0340](angular/angular.js@74da034),
   [elastic#12975](angular/angular.js#12975), [elastic#12936](angular/angular.js#12936), [elastic#13244](angular/angular.js#13244))
- **$http:** apply `transformResponse` even when `data` is empty
  ([c6909464](angular/angular.js@c690946),
   [elastic#12976](angular/angular.js#12976), [elastic#12979](angular/angular.js#12979))
- **$location:** ensure `$locationChangeSuccess` fires even if URL ends with `#`
  ([6f8ddb6d](angular/angular.js@6f8ddb6),
   [elastic#12175](angular/angular.js#12175), [elastic#13251](angular/angular.js#13251))
- **$parse:** evaluate once simple expressions only once
  ([e4036824](angular/angular.js@e403682),
   [elastic#12983](angular/angular.js#12983), [elastic#13002](angular/angular.js#13002))
- **$resource:** allow XHR request to be cancelled via a timeout promise
  ([7170f9d9](angular/angular.js@7170f9d),
   [elastic#12657](angular/angular.js#12657), [elastic#12675](angular/angular.js#12675), [elastic#10890](angular/angular.js#10890), [elastic#9332](angular/angular.js#9332))
- **$rootScope:** prevent IE9 memory leak when destroying scopes
  ([87b0055c](angular/angular.js@87b0055),
   [elastic#10706](angular/angular.js#10706), [elastic#11786](angular/angular.js#11786))
- **Angular.js:** fix `isArrayLike` for unusual cases
  ([70edec94](angular/angular.js@70edec9),
   [elastic#10186](angular/angular.js#10186), [elastic#8000](angular/angular.js#8000), [elastic#4855](angular/angular.js#4855), [elastic#4751](angular/angular.js#4751), [elastic#10272](angular/angular.js#10272))
- **isArrayLike:** handle jQuery objects of length 0
  ([d3da55c4](angular/angular.js@d3da55c))
- **jqLite:**
  - deregister special `mouseenter` / `mouseleave` events correctly
  ([22f66025](angular/angular.js@22f6602),
   [elastic#12795](angular/angular.js#12795), [elastic#12799](angular/angular.js#12799))
  - ensure mouseenter works with svg elements on IE
  ([c1f34e8e](angular/angular.js@c1f34e8),
   [elastic#10259](angular/angular.js#10259), [elastic#10276](angular/angular.js#10276))
- **limitTo:** start at 0 if `begin` is negative and exceeds input length
  ([4fc40bc9](angular/angular.js@4fc40bc),
   [elastic#12775](angular/angular.js#12775), [elastic#12781](angular/angular.js#12781))
- **merge:**
  - ensure that jqlite->jqlite and DOM->DOM
  ([2f8db1bf](angular/angular.js@2f8db1b))
  - clone elements instead of treating them like simple objects
  ([838cf4be](angular/angular.js@838cf4b),
   [elastic#12286](angular/angular.js#12286))
- **ngAria:** don't add tabindex to radio and checkbox inputs
  ([59f1f4e1](angular/angular.js@59f1f4e),
   [elastic#12492](angular/angular.js#12492), [elastic#13095](angular/angular.js#13095))
- **ngInput:** change URL_REGEXP to better match RFC3987
  ([cb51116d](angular/angular.js@cb51116),
   [elastic#11341](angular/angular.js#11341), [elastic#11381](angular/angular.js#11381))
- **ngMock:** reset cache before every test
  ([91b7cd9b](angular/angular.js@91b7cd9),
   [elastic#13013](angular/angular.js#13013))
- **ngOptions:**
  - skip comments and empty options when looking for options
  ([0f58334b](angular/angular.js@0f58334),
   [elastic#12190](angular/angular.js#12190), [elastic#13029](angular/angular.js#13029), [elastic#13033](angular/angular.js#13033))
  - override select option registration to allow compilation of empty option
  ([7b2ecf4](angular/angular.js@7b2ecf4),
   [elastic#11685](angular/angular.js#11685), [elastic#12972](angular/angular.js#12972), [elastic#12968](angular/angular.js#12968), [elastic#13012](angular/angular.js#13012))

## Performance Improvements

- **$compile:** use static jquery data method to avoid creating new instances
  ([55ad192e](angular/angular.js@55ad192))
- **copy:**
  - avoid regex in `isTypedArray`
  ([19fab4a1](angular/angular.js@19fab4a))
  - only validate/clear if the user specifies a destination
  ([d1293540](angular/angular.js@d129354),
   [elastic#12068](angular/angular.js#12068))
- **merge:** remove unnecessary wrapping of jqLite element
  ([ce6a96b0](angular/angular.js@ce6a96b),
   [elastic#13236](angular/angular.js#13236))

## Breaking Changes

Signed-off-by: Kevin Kirsche <Kev.Kirsche@gmail.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.