Skip to content

Commit

Permalink
feat($animate): use promises instead of callbacks for animations
Browse files Browse the repository at this point in the history
The $animate service (both the service inside of ng and ngAnimate) now
makes use of promises instead of callback functions.

BREAKING CHANGE

Both the API for the cancallation method and the done callback for
$animate animations is different. Instead of using a callback function
for each of the $animate animation methods, a promise is used instead.

```js
//before
$animate.enter(element, container, null, callbackFn);

//after
$animate.enter(element, container).then(callbackFn);
```

The animation can now be cancelled via `$animate.cancel(promise)`.

```js
//before
var cancelFn = $animate.enter(element, container);
cancelFn(); //cancels the animation

//after
var promise = $animate.enter(element, container);
$animate.cancel(promise); //cancels the animation
```
  • Loading branch information
matsko committed Aug 26, 2014
1 parent 2f4437b commit bf0f550
Show file tree
Hide file tree
Showing 16 changed files with 312 additions and 268 deletions.
99 changes: 49 additions & 50 deletions src/ng/animate.js
Expand Up @@ -81,10 +81,20 @@ var $AnimateProvider = ['$provide', function($provide) {
return this.$$classNameFilter;
};

this.$get = ['$timeout', '$$asyncCallback', function($timeout, $$asyncCallback) {

function async(fn) {
fn && $$asyncCallback(fn);
this.$get = ['$$q', '$$asyncCallback', function($$q, $$asyncCallback) {

var currentDefer;
function asyncPromise() {
// only serve one instance of a promise in order to save CPU cycles
if (!currentDefer) {
currentDefer = $$q.defer();
currentDefer.promise.cancel = noop; //ngAnimate.$animate provides this
$$asyncCallback(function() {
currentDefer.resolve();
currentDefer = null;
});
}
return currentDefer.promise;
}

/**
Expand Down Expand Up @@ -112,39 +122,34 @@ var $AnimateProvider = ['$provide', function($provide) {
* @name $animate#enter
* @kind function
* @description Inserts the element into the DOM either after the `after` element or
* as the first child within the `parent` element. Once complete, the done() callback
* will be fired (if provided).
* as the first child within the `parent` element. When the function is called a promise
* is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will be inserted into the DOM
* @param {DOMElement} parent the parent element which will append the element as
* a child (if the after element is not present)
* @param {DOMElement} after the sibling element which will append the element
* after itself
* @param {Function=} done callback function that will be called after the element has been
* inserted into the DOM
* @return {Promise} the animation callback promise
*/
enter : function(element, parent, after, done) {
after
? after.after(element)
: parent.prepend(element);
async(done);
return noop;
enter : function(element, parent, after) {
after ? after.after(element)
: parent.prepend(element);
return asyncPromise();
},

/**
*
* @ngdoc method
* @name $animate#leave
* @kind function
* @description Removes the element from the DOM. Once complete, the done() callback will be
* fired (if provided).
* @description Removes the element from the DOM. When the function is called a promise
* is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will be removed from the DOM
* @param {Function=} done callback function that will be called after the element has been
* removed from the DOM
* @return {Promise} the animation callback promise
*/
leave : function(element, done) {
leave : function(element) {
element.remove();
async(done);
return noop;
return asyncPromise();
},

/**
Expand All @@ -153,46 +158,43 @@ var $AnimateProvider = ['$provide', function($provide) {
* @name $animate#move
* @kind function
* @description Moves the position of the provided element within the DOM to be placed
* either after the `after` element or inside of the `parent` element. Once complete, the
* done() callback will be fired (if provided).
* either after the `after` element or inside of the `parent` element. When the function
* is called a promise is returned that will be resolved at a later time.
*
* @param {DOMElement} element the element which will be moved around within the
* DOM
* @param {DOMElement} parent the parent element where the element will be
* inserted into (if the after element is not present)
* @param {DOMElement} after the sibling element where the element will be
* positioned next to
* @param {Function=} done the callback function (if provided) that will be fired after the
* element has been moved to its new position
* @return {Promise} the animation callback promise
*/
move : function(element, parent, after, done) {
move : function(element, parent, after) {
// Do not remove element before insert. Removing will cause data associated with the
// element to be dropped. Insert will implicitly do the remove.
return this.enter(element, parent, after, done);
return this.enter(element, parent, after);
},

/**
*
* @ngdoc method
* @name $animate#addClass
* @kind function
* @description Adds the provided className CSS class value to the provided element. Once
* complete, the done() callback will be fired (if provided).
* @description Adds the provided className CSS class value to the provided element.
* When the function is called a promise is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will have the className value
* added to it
* @param {string} className the CSS class which will be added to the element
* @param {Function=} done the callback function (if provided) that will be fired after the
* className value has been added to the element
* @return {Promise} the animation callback promise
*/
addClass : function(element, className, done) {
addClass : function(element, className) {
className = !isString(className)
? (isArray(className) ? className.join(' ') : '')
: className;
forEach(element, function (element) {
jqLiteAddClass(element, className);
});
async(done);
return noop;
return asyncPromise();
},

/**
Expand All @@ -201,22 +203,20 @@ var $AnimateProvider = ['$provide', function($provide) {
* @name $animate#removeClass
* @kind function
* @description Removes the provided className CSS class value from the provided element.
* Once complete, the done() callback will be fired (if provided).
* When the function is called a promise is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will have the className value
* removed from it
* @param {string} className the CSS class which will be removed from the element
* @param {Function=} done the callback function (if provided) that will be fired after the
* className value has been removed from the element
* @return {Promise} the animation callback promise
*/
removeClass : function(element, className, done) {
className = isString(className) ?
className :
isArray(className) ? className.join(' ') : '';
removeClass : function(element, className) {
className = !isString(className)
? (isArray(className) ? className.join(' ') : '')
: className;
forEach(element, function (element) {
jqLiteRemoveClass(element, className);
});
async(done);
return noop;
return asyncPromise();
},

/**
Expand All @@ -225,22 +225,21 @@ var $AnimateProvider = ['$provide', function($provide) {
* @name $animate#setClass
* @kind function
* @description Adds and/or removes the given CSS classes to and from the element.
* Once complete, the done() callback will be fired (if provided).
* When the function is called a promise is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will have its CSS classes changed
* removed from it
* @param {string} add the CSS classes which will be added to the element
* @param {string} remove the CSS class which will be removed from the element
* @param {Function=} done the callback function (if provided) that will be fired after the
* CSS classes have been set on the element
* @return {Promise} the animation callback promise
*/
setClass : function(element, add, remove, done) {
setClass : function(element, add, remove) {
this.addClass(element, add);
this.removeClass(element, remove);
async(done);
return noop;
return asyncPromise();
},

enabled : noop
enabled : noop,
cancel : noop
};
}];
}];
2 changes: 1 addition & 1 deletion src/ng/directive/ngIf.js
Expand Up @@ -113,7 +113,7 @@ var ngIfDirective = ['$animate', function($animate) {
}
if(block) {
previousElements = getBlockNodes(block.clone);
$animate.leave(previousElements, function() {
$animate.leave(previousElements).then(function() {
previousElements = null;
});
block = null;
Expand Down
4 changes: 2 additions & 2 deletions src/ng/directive/ngInclude.js
Expand Up @@ -198,7 +198,7 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$animate'
currentScope = null;
}
if(currentElement) {
$animate.leave(currentElement, function() {
$animate.leave(currentElement).then(function() {
previousElement = null;
});
previousElement = currentElement;
Expand Down Expand Up @@ -228,7 +228,7 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$animate'
// directives to non existing elements.
var clone = $transclude(newScope, function(clone) {
cleanupLastIncludeContent();
$animate.enter(clone, null, $element, afterAnimation);
$animate.enter(clone, null, $element).then(afterAnimation);
});

currentScope = newScope;
Expand Down
2 changes: 1 addition & 1 deletion src/ng/directive/ngSwitch.js
Expand Up @@ -155,7 +155,7 @@ var ngSwitchDirective = ['$animate', function($animate) {
var selected = getBlockNodes(selectedElements[i].clone);
selectedScopes[i].$destroy();
previousElements[i] = selected;
$animate.leave(selected, function() {
$animate.leave(selected).then(function() {
previousElements.splice(i, 1);
});
}
Expand Down

0 comments on commit bf0f550

Please sign in to comment.