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

Commit

Permalink
feat(ngAnimate): expose a core version of $animateCss
Browse files Browse the repository at this point in the history
A core version of `$animateCss` can now be injected when
ngAnimate is not present. This core version doesn't trigger any
animations in any way. All that it does is apply the provided from
and/or to styles as well as the addClass and removeClass values.

The motivation for this feature is to allow for directives to activate
animations automatically when ngAnimate is included without the need to
use `$animate`.

Closes #12509
Closes #12570
  • Loading branch information
matsko committed Aug 13, 2015
1 parent cf28c1a commit 39b634e
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 0 deletions.
1 change: 1 addition & 0 deletions angularFiles.js
Expand Up @@ -14,6 +14,7 @@ var angularFiles = {

'src/ng/anchorScroll.js',
'src/ng/animate.js',
'src/ng/animateCss.js',
'src/ng/browser.js',
'src/ng/cacheFactory.js',
'src/ng/compile.js',
Expand Down
2 changes: 2 additions & 0 deletions src/AngularPublic.js
Expand Up @@ -55,6 +55,7 @@
$AnchorScrollProvider,
$AnimateProvider,
$CoreAnimateCssProvider,
$$CoreAnimateQueueProvider,
$$CoreAnimateRunnerProvider,
$BrowserProvider,
Expand Down Expand Up @@ -212,6 +213,7 @@ function publishExternalAPI(angular) {
$provide.provider({
$anchorScroll: $AnchorScrollProvider,
$animate: $AnimateProvider,
$animateCss: $CoreAnimateCssProvider,
$$animateQueue: $$CoreAnimateQueueProvider,
$$AnimateRunner: $$CoreAnimateRunnerProvider,
$browser: $BrowserProvider,
Expand Down
84 changes: 84 additions & 0 deletions src/ng/animateCss.js
@@ -0,0 +1,84 @@
'use strict';

/**
* @ngdoc service
* @name $animateCss
* @kind object
*
* @description
* This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included,
* then the `$animateCss` service will actually perform animations.
*
* Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
*/
var $CoreAnimateCssProvider = function() {
this.$get = ['$$rAF', '$q', function($$rAF, $q) {

var RAFPromise = function() {};
RAFPromise.prototype = {
done: function(cancel) {
this.defer && this.defer[cancel === true ? 'reject' : 'resolve']();
},
end: function() {
this.done();
},
cancel: function() {
this.done(true);
},
getPromise: function() {
if (!this.defer) {
this.defer = $q.defer();
}
return this.defer.promise;
},
then: function(f1,f2) {
return this.getPromise().then(f1,f2);
},
'catch': function(f1) {
return this.getPromise().catch(f1);
},
'finally': function(f1) {
return this.getPromise().finally(f1);
}
};

return function(element, options) {
if (options.from) {
element.css(options.from);
options.from = null;
}

var closed, runner = new RAFPromise();
return {
start: run,
end: run
};

function run() {
$$rAF(function() {
close();
if (!closed) {
runner.done();
}
closed = true;
});
return runner;
}

function close() {
if (options.addClass) {
element.addClass(options.addClass);
options.addClass = null;
}
if (options.removeClass) {
element.removeClass(options.removeClass);
options.removeClass = null;
}
if (options.to) {
element.css(options.to);
options.to = null;
}
}
};
}];
};
120 changes: 120 additions & 0 deletions test/ng/animateCssSpec.js
@@ -0,0 +1,120 @@
'use strict';

describe("$animateCss", function() {

var triggerRAF, element;
beforeEach(inject(function($$rAF, $rootElement, $document) {
triggerRAF = function() {
$$rAF.flush();
};

var body = jqLite($document[0].body);
element = jqLite('<div></div>');
$rootElement.append(element);
body.append($rootElement);
}));

describe("without animation", function() {

it("should apply the provided [from] CSS to the element", inject(function($animateCss) {
$animateCss(element, { from: { height: '50px' }}).start();
expect(element.css('height')).toBe('50px');
}));

it("should apply the provided [to] CSS to the element after the first frame", inject(function($animateCss) {
$animateCss(element, { to: { width: '50px' }}).start();
expect(element.css('width')).not.toBe('50px');
triggerRAF();
expect(element.css('width')).toBe('50px');
}));

it("should apply the provided [addClass] CSS classes to the element after the first frame", inject(function($animateCss) {
$animateCss(element, { addClass: 'golden man' }).start();
expect(element).not.toHaveClass('golden man');
triggerRAF();
expect(element).toHaveClass('golden man');
}));

it("should apply the provided [removeClass] CSS classes to the element after the first frame", inject(function($animateCss) {
element.addClass('silver');
$animateCss(element, { removeClass: 'silver dude' }).start();
expect(element).toHaveClass('silver');
triggerRAF();
expect(element).not.toHaveClass('silver');
}));

it("should return an animator with a start method which returns a promise", inject(function($animateCss) {
var promise = $animateCss(element, { addClass: 'cool' }).start();
expect(isPromiseLike(promise)).toBe(true);
}));

it("should return an animator with an end method which returns a promise", inject(function($animateCss) {
var promise = $animateCss(element, { addClass: 'cool' }).end();
expect(isPromiseLike(promise)).toBe(true);
}));

it("should only resolve the promise once both a digest and RAF have passed after start",
inject(function($animateCss, $rootScope) {

var doneSpy = jasmine.createSpy();
var runner = $animateCss(element, { addClass: 'cool' }).start();

runner.then(doneSpy);
expect(doneSpy).not.toHaveBeenCalled();

triggerRAF();
expect(doneSpy).not.toHaveBeenCalled();

$rootScope.$digest();
expect(doneSpy).toHaveBeenCalled();
}));

it("should resolve immediately if runner.end() is called",
inject(function($animateCss, $rootScope) {

var doneSpy = jasmine.createSpy();
var runner = $animateCss(element, { addClass: 'cool' }).start();

runner.then(doneSpy);
runner.end();
expect(doneSpy).not.toHaveBeenCalled();

$rootScope.$digest();
expect(doneSpy).toHaveBeenCalled();
}));

it("should reject immediately if runner.end() is called",
inject(function($animateCss, $rootScope) {

var cancelSpy = jasmine.createSpy();
var runner = $animateCss(element, { addClass: 'cool' }).start();

runner.catch(cancelSpy);
runner.cancel();
expect(cancelSpy).not.toHaveBeenCalled();

$rootScope.$digest();
expect(cancelSpy).toHaveBeenCalled();
}));

it("should not resolve after the next frame if the runner has already been cancelled",
inject(function($animateCss, $rootScope) {

var doneSpy = jasmine.createSpy();
var cancelSpy = jasmine.createSpy();
var runner = $animateCss(element, { addClass: 'cool' }).start();

runner.then(doneSpy, cancelSpy);
runner.cancel();

$rootScope.$digest();
expect(cancelSpy).toHaveBeenCalled();
expect(doneSpy).not.toHaveBeenCalled();

triggerRAF();
expect(cancelSpy).toHaveBeenCalled();
expect(doneSpy).not.toHaveBeenCalled();
}));
});

});

0 comments on commit 39b634e

Please sign in to comment.