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

Commit 39b634e

Browse files
committed
feat(ngAnimate): expose a core version of $animateCss
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
1 parent cf28c1a commit 39b634e

File tree

4 files changed

+207
-0
lines changed

4 files changed

+207
-0
lines changed

angularFiles.js

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ var angularFiles = {
1414

1515
'src/ng/anchorScroll.js',
1616
'src/ng/animate.js',
17+
'src/ng/animateCss.js',
1718
'src/ng/browser.js',
1819
'src/ng/cacheFactory.js',
1920
'src/ng/compile.js',

src/AngularPublic.js

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
5656
$AnchorScrollProvider,
5757
$AnimateProvider,
58+
$CoreAnimateCssProvider,
5859
$$CoreAnimateQueueProvider,
5960
$$CoreAnimateRunnerProvider,
6061
$BrowserProvider,
@@ -212,6 +213,7 @@ function publishExternalAPI(angular) {
212213
$provide.provider({
213214
$anchorScroll: $AnchorScrollProvider,
214215
$animate: $AnimateProvider,
216+
$animateCss: $CoreAnimateCssProvider,
215217
$$animateQueue: $$CoreAnimateQueueProvider,
216218
$$AnimateRunner: $$CoreAnimateRunnerProvider,
217219
$browser: $BrowserProvider,

src/ng/animateCss.js

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
'use strict';
2+
3+
/**
4+
* @ngdoc service
5+
* @name $animateCss
6+
* @kind object
7+
*
8+
* @description
9+
* This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included,
10+
* then the `$animateCss` service will actually perform animations.
11+
*
12+
* Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
13+
*/
14+
var $CoreAnimateCssProvider = function() {
15+
this.$get = ['$$rAF', '$q', function($$rAF, $q) {
16+
17+
var RAFPromise = function() {};
18+
RAFPromise.prototype = {
19+
done: function(cancel) {
20+
this.defer && this.defer[cancel === true ? 'reject' : 'resolve']();
21+
},
22+
end: function() {
23+
this.done();
24+
},
25+
cancel: function() {
26+
this.done(true);
27+
},
28+
getPromise: function() {
29+
if (!this.defer) {
30+
this.defer = $q.defer();
31+
}
32+
return this.defer.promise;
33+
},
34+
then: function(f1,f2) {
35+
return this.getPromise().then(f1,f2);
36+
},
37+
'catch': function(f1) {
38+
return this.getPromise().catch(f1);
39+
},
40+
'finally': function(f1) {
41+
return this.getPromise().finally(f1);
42+
}
43+
};
44+
45+
return function(element, options) {
46+
if (options.from) {
47+
element.css(options.from);
48+
options.from = null;
49+
}
50+
51+
var closed, runner = new RAFPromise();
52+
return {
53+
start: run,
54+
end: run
55+
};
56+
57+
function run() {
58+
$$rAF(function() {
59+
close();
60+
if (!closed) {
61+
runner.done();
62+
}
63+
closed = true;
64+
});
65+
return runner;
66+
}
67+
68+
function close() {
69+
if (options.addClass) {
70+
element.addClass(options.addClass);
71+
options.addClass = null;
72+
}
73+
if (options.removeClass) {
74+
element.removeClass(options.removeClass);
75+
options.removeClass = null;
76+
}
77+
if (options.to) {
78+
element.css(options.to);
79+
options.to = null;
80+
}
81+
}
82+
};
83+
}];
84+
};

test/ng/animateCssSpec.js

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
'use strict';
2+
3+
describe("$animateCss", function() {
4+
5+
var triggerRAF, element;
6+
beforeEach(inject(function($$rAF, $rootElement, $document) {
7+
triggerRAF = function() {
8+
$$rAF.flush();
9+
};
10+
11+
var body = jqLite($document[0].body);
12+
element = jqLite('<div></div>');
13+
$rootElement.append(element);
14+
body.append($rootElement);
15+
}));
16+
17+
describe("without animation", function() {
18+
19+
it("should apply the provided [from] CSS to the element", inject(function($animateCss) {
20+
$animateCss(element, { from: { height: '50px' }}).start();
21+
expect(element.css('height')).toBe('50px');
22+
}));
23+
24+
it("should apply the provided [to] CSS to the element after the first frame", inject(function($animateCss) {
25+
$animateCss(element, { to: { width: '50px' }}).start();
26+
expect(element.css('width')).not.toBe('50px');
27+
triggerRAF();
28+
expect(element.css('width')).toBe('50px');
29+
}));
30+
31+
it("should apply the provided [addClass] CSS classes to the element after the first frame", inject(function($animateCss) {
32+
$animateCss(element, { addClass: 'golden man' }).start();
33+
expect(element).not.toHaveClass('golden man');
34+
triggerRAF();
35+
expect(element).toHaveClass('golden man');
36+
}));
37+
38+
it("should apply the provided [removeClass] CSS classes to the element after the first frame", inject(function($animateCss) {
39+
element.addClass('silver');
40+
$animateCss(element, { removeClass: 'silver dude' }).start();
41+
expect(element).toHaveClass('silver');
42+
triggerRAF();
43+
expect(element).not.toHaveClass('silver');
44+
}));
45+
46+
it("should return an animator with a start method which returns a promise", inject(function($animateCss) {
47+
var promise = $animateCss(element, { addClass: 'cool' }).start();
48+
expect(isPromiseLike(promise)).toBe(true);
49+
}));
50+
51+
it("should return an animator with an end method which returns a promise", inject(function($animateCss) {
52+
var promise = $animateCss(element, { addClass: 'cool' }).end();
53+
expect(isPromiseLike(promise)).toBe(true);
54+
}));
55+
56+
it("should only resolve the promise once both a digest and RAF have passed after start",
57+
inject(function($animateCss, $rootScope) {
58+
59+
var doneSpy = jasmine.createSpy();
60+
var runner = $animateCss(element, { addClass: 'cool' }).start();
61+
62+
runner.then(doneSpy);
63+
expect(doneSpy).not.toHaveBeenCalled();
64+
65+
triggerRAF();
66+
expect(doneSpy).not.toHaveBeenCalled();
67+
68+
$rootScope.$digest();
69+
expect(doneSpy).toHaveBeenCalled();
70+
}));
71+
72+
it("should resolve immediately if runner.end() is called",
73+
inject(function($animateCss, $rootScope) {
74+
75+
var doneSpy = jasmine.createSpy();
76+
var runner = $animateCss(element, { addClass: 'cool' }).start();
77+
78+
runner.then(doneSpy);
79+
runner.end();
80+
expect(doneSpy).not.toHaveBeenCalled();
81+
82+
$rootScope.$digest();
83+
expect(doneSpy).toHaveBeenCalled();
84+
}));
85+
86+
it("should reject immediately if runner.end() is called",
87+
inject(function($animateCss, $rootScope) {
88+
89+
var cancelSpy = jasmine.createSpy();
90+
var runner = $animateCss(element, { addClass: 'cool' }).start();
91+
92+
runner.catch(cancelSpy);
93+
runner.cancel();
94+
expect(cancelSpy).not.toHaveBeenCalled();
95+
96+
$rootScope.$digest();
97+
expect(cancelSpy).toHaveBeenCalled();
98+
}));
99+
100+
it("should not resolve after the next frame if the runner has already been cancelled",
101+
inject(function($animateCss, $rootScope) {
102+
103+
var doneSpy = jasmine.createSpy();
104+
var cancelSpy = jasmine.createSpy();
105+
var runner = $animateCss(element, { addClass: 'cool' }).start();
106+
107+
runner.then(doneSpy, cancelSpy);
108+
runner.cancel();
109+
110+
$rootScope.$digest();
111+
expect(cancelSpy).toHaveBeenCalled();
112+
expect(doneSpy).not.toHaveBeenCalled();
113+
114+
triggerRAF();
115+
expect(cancelSpy).toHaveBeenCalled();
116+
expect(doneSpy).not.toHaveBeenCalled();
117+
}));
118+
});
119+
120+
});

0 commit comments

Comments
 (0)