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

Commit 78297d2

Browse files
committed
feat(ngAnimate): introduce ngAnimateSwap directive
1 parent e5e0884 commit 78297d2

File tree

4 files changed

+280
-0
lines changed

4 files changed

+280
-0
lines changed

angularFiles.js

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ var angularFiles = {
101101
'src/ngAnimate/animateQueue.js',
102102
'src/ngAnimate/animateRunner.js',
103103
'src/ngAnimate/animation.js',
104+
'src/ngAnimate/ngAnimateSwap.js',
104105
'src/ngAnimate/module.js'
105106
],
106107
'ngCookies': [

src/ngAnimate/module.js

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
/* global angularAnimateModule: true,
44
5+
ngAnimateSwapDirective,
56
$$AnimateAsyncRunFactory,
67
$$rAFSchedulerFactory,
78
$$AnimateChildrenDirective,
@@ -738,6 +739,8 @@
738739
* Click here {@link ng.$animate to learn more about animations with `$animate`}.
739740
*/
740741
angular.module('ngAnimate', [])
742+
.directive('ngAnimateSwap', ngAnimateSwapDirective)
743+
741744
.directive('ngAnimateChildren', $$AnimateChildrenDirective)
742745
.factory('$$rAFScheduler', $$rAFSchedulerFactory)
743746

src/ngAnimate/ngAnimateSwap.js

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
'use strict';
2+
3+
/**
4+
* @ngdoc directive
5+
* @name ngAnimateSwap
6+
* @restrict A
7+
* @scope
8+
*
9+
* @description
10+
*
11+
* ngAnimateSwap is a animation-oriented directive that allows for the container to
12+
* be removed and entered in whenever the associated expression changes. A
13+
* common usecase for this directive is a rotating banner component which
14+
* contains one image being present at a time. When the active image changes
15+
* then the old image will perform a `leave` animation and the new element
16+
* will be inserted via an `enter` animation.
17+
*
18+
* @example
19+
* <example name="ngAnimateSwap-directive" module="ngAnimateSwapExample"
20+
* deps="angular-animate.js"
21+
* animations="true" fixBase="true">
22+
* <file name="index.html">
23+
* <div class="container" ng-controller="AppCtrl">
24+
* <div ng-animate-swap="number" class="cell swap-animation" ng-class="colorClass(number)">
25+
* {{ number }}
26+
* </div>
27+
* </div>
28+
* </file>
29+
* <file name="script.js">
30+
* angular.module('ngAnimateSwapExample', ['ngAnimate'])
31+
* .controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) {
32+
* $scope.number = 0;
33+
* $interval(function() {
34+
* $scope.number++;
35+
* }, 1000);
36+
*
37+
* var colors = ['red','blue','green','yellow','orange'];
38+
* $scope.colorClass = function(number) {
39+
* return colors[number % colors.length];
40+
* };
41+
* }]);
42+
* </file>
43+
* <file name="animations.css">
44+
* .container {
45+
* height:250px;
46+
* width:250px;
47+
* position:relative;
48+
* overflow:hidden;
49+
* border:2px solid black;
50+
* }
51+
* .container .cell {
52+
* font-size:150px;
53+
* text-align:center;
54+
* line-height:250px;
55+
* position:absolute;
56+
* top:0;
57+
* left:0;
58+
* right:0;
59+
* border-bottom:2px solid black;
60+
* }
61+
* .swap-animation.ng-enter, .swap-animation.ng-leave {
62+
* transition:0.5s linear all;
63+
* }
64+
* .swap-animation.ng-enter {
65+
* top:-250px;
66+
* }
67+
* .swap-animation.ng-enter-active {
68+
* top:0px;
69+
* }
70+
* .swap-animation.ng-leave {
71+
* top:0px;
72+
* }
73+
* .swap-animation.ng-leave-active {
74+
* top:250px;
75+
* }
76+
* .red { background:red; }
77+
* .green { background:green; }
78+
* .blue { background:blue; }
79+
* .yellow { background:yellow; }
80+
* .orange { background:orange; }
81+
* </file>
82+
* </example>
83+
*/
84+
var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) {
85+
return {
86+
restrict: 'A',
87+
transclude: 'element',
88+
terminal: true,
89+
priority: 600, // we use 600 here to ensure that the directive is caught before others
90+
link: function(scope, $element, attrs, ctrl, $transclude) {
91+
var previousElement, previousScope;
92+
scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) {
93+
if (previousElement) {
94+
$animate.leave(previousElement);
95+
}
96+
if (previousScope) {
97+
previousScope.$destroy();
98+
previousScope = null;
99+
}
100+
if (value || value === 0) {
101+
previousScope = scope.$new();
102+
$transclude(previousScope, function(element) {
103+
previousElement = element;
104+
$animate.enter(element, null, $element);
105+
});
106+
}
107+
});
108+
}
109+
};
110+
}];

test/ngAnimate/ngAnimateSwapSpec.js

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
'use strict';
2+
3+
describe("ngAnimateSwap", function() {
4+
5+
beforeEach(module('ngAnimate'));
6+
beforeEach(module('ngAnimateMock'));
7+
8+
var element;
9+
afterEach(function() {
10+
dealoc(element);
11+
});
12+
13+
var $rootScope, $compile, $animate;
14+
beforeEach(inject(function(_$rootScope_, _$animate_, _$compile_) {
15+
$rootScope = _$rootScope_;
16+
$animate = _$animate_;
17+
$compile = _$compile_;
18+
19+
$animate.enabled(false);
20+
}));
21+
22+
23+
it('should render a new container when the expression changes', inject(function() {
24+
element = $compile('<div><div ng-animate-swap="exp">{{ exp }}</div></div>')($rootScope);
25+
$rootScope.$digest();
26+
27+
var first = element.find('div')[0];
28+
expect(first).toBeFalsy();
29+
30+
$rootScope.exp = 'yes';
31+
$rootScope.$digest();
32+
33+
var second = element.find('div')[0];
34+
expect(second.textContent).toBe('yes');
35+
36+
$rootScope.exp = 'super';
37+
$rootScope.$digest();
38+
39+
var third = element.find('div')[0];
40+
expect(third.textContent).toBe('super');
41+
expect(third).not.toEqual(second);
42+
expect(second.parentNode).toBeFalsy();
43+
}));
44+
45+
it('should render a new container only when the expression property changes', inject(function() {
46+
element = $compile('<div><div ng-animate-swap="exp.prop">{{ exp.value }}</div></div>')($rootScope);
47+
$rootScope.exp = {
48+
prop: 'hello',
49+
value: 'world'
50+
};
51+
$rootScope.$digest();
52+
53+
var one = element.find('div')[0];
54+
expect(one.textContent).toBe('world');
55+
56+
$rootScope.exp.value = 'planet';
57+
$rootScope.$digest();
58+
59+
var two = element.find('div')[0];
60+
expect(two.textContent).toBe('planet');
61+
expect(two).toBe(one);
62+
63+
$rootScope.exp.prop = 'goodbye';
64+
$rootScope.$digest();
65+
66+
var three = element.find('div')[0];
67+
expect(three.textContent).toBe('planet');
68+
expect(three).not.toBe(two);
69+
}));
70+
71+
it('should watch the expression as a collection', inject(function() {
72+
element = $compile('<div><div ng-animate-swap="exp">{{ exp.a }} {{ exp.b }} {{ exp.c }}</div></div>')($rootScope);
73+
$rootScope.exp = {
74+
a: 1,
75+
b: 2
76+
};
77+
$rootScope.$digest();
78+
79+
var one = element.find('div')[0];
80+
expect(one.textContent.trim()).toBe('1 2');
81+
82+
$rootScope.exp.a++;
83+
$rootScope.$digest();
84+
85+
var two = element.find('div')[0];
86+
expect(two.textContent.trim()).toBe('2 2');
87+
expect(two).not.toEqual(one);
88+
89+
$rootScope.exp.c = 3;
90+
$rootScope.$digest();
91+
92+
var three = element.find('div')[0];
93+
expect(three.textContent.trim()).toBe('2 2 3');
94+
expect(three).not.toEqual(two);
95+
96+
$rootScope.exp = { c: 4 };
97+
$rootScope.$digest();
98+
99+
var four = element.find('div')[0];
100+
expect(four.textContent.trim()).toBe('4');
101+
expect(four).not.toEqual(three);
102+
}));
103+
104+
they('should consider $prop as a falsy value', [false, undefined, null], function(value) {
105+
inject(function() {
106+
element = $compile('<div><div ng-animate-swap="value">{{ value }}</div></div>')($rootScope);
107+
$rootScope.value = true;
108+
$rootScope.$digest();
109+
110+
var one = element.find('div')[0];
111+
expect(one).toBeTruthy();
112+
113+
$rootScope.value = value;
114+
$rootScope.$digest();
115+
116+
var two = element.find('div')[0];
117+
expect(two).toBeFalsy();
118+
});
119+
});
120+
121+
it('should consider "0" as a truthy value', inject(function() {
122+
element = $compile('<div><div ng-animate-swap="value">{{ value }}</div></div>')($rootScope);
123+
$rootScope.$digest();
124+
125+
var one = element.find('div')[0];
126+
expect(one).toBeFalsy();
127+
128+
$rootScope.value = 0;
129+
$rootScope.$digest();
130+
131+
var two = element.find('div')[0];
132+
expect(two).toBeTruthy();
133+
}));
134+
135+
136+
describe("animations", function() {
137+
it('should trigger a leave animation followed by an enter animation upon swap',
138+
inject(function() {
139+
140+
element = $compile('<div><div ng-animate-swap="exp">{{ exp }}</div></div>')($rootScope);
141+
$rootScope.exp = 1;
142+
$rootScope.$digest();
143+
144+
var first = $animate.queue.shift();
145+
expect(first.event).toBe('enter');
146+
expect($animate.queue.length).toBe(0);
147+
148+
$rootScope.exp = 2;
149+
$rootScope.$digest();
150+
151+
var second = $animate.queue.shift();
152+
expect(second.event).toBe('leave');
153+
154+
var third = $animate.queue.shift();
155+
expect(third.event).toBe('enter');
156+
expect($animate.queue.length).toBe(0);
157+
158+
$rootScope.exp = false;
159+
$rootScope.$digest();
160+
161+
var forth = $animate.queue.shift();
162+
expect(forth.event).toBe('leave');
163+
expect($animate.queue.length).toBe(0);
164+
}));
165+
});
166+
});

0 commit comments

Comments
 (0)