Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 44108d7

Browse files
committedOct 8, 2014
feat($animate): enable temporary classes to be applied during an animation
1 parent 74a214c commit 44108d7

File tree

3 files changed

+129
-18
lines changed

3 files changed

+129
-18
lines changed
 

‎src/ng/directive/ngShowHide.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
var NG_HIDE_CLASS = 'ng-hide';
4+
var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
35
/**
46
* @ngdoc directive
57
* @name ngShow
@@ -161,7 +163,11 @@ var ngShowDirective = ['$animate', function($animate) {
161163
multiElement: true,
162164
link: function(scope, element, attr) {
163165
scope.$watch(attr.ngShow, function ngShowWatchAction(value){
164-
$animate[value ? 'removeClass' : 'addClass'](element, 'ng-hide');
166+
// we're adding a temporary, animation-specific class for ng-hide since this way
167+
// we can control when the element is actually displayed on screen without having
168+
// to have a global/greedy CSS selector that breaks when other animations are run.
169+
// Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
170+
$animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, NG_HIDE_IN_PROGRESS_CLASS);
165171
});
166172
}
167173
};
@@ -316,7 +322,9 @@ var ngHideDirective = ['$animate', function($animate) {
316322
multiElement: true,
317323
link: function(scope, element, attr) {
318324
scope.$watch(attr.ngHide, function ngHideWatchAction(value){
319-
$animate[value ? 'addClass' : 'removeClass'](element, 'ng-hide');
325+
// The comment inside of the ngShowDirective explains why we add and
326+
// remove a temporary class for the show/hide animation
327+
$animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, NG_HIDE_IN_PROGRESS_CLASS);
320328
});
321329
}
322330
};

‎src/ngAnimate/animate.js

+47-16
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ angular.module('ngAnimate', ['ng'])
342342
var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren';
343343
return function(scope, element, attrs) {
344344
var val = attrs.ngAnimateChildren;
345-
if (angular.isString(val) && val.length === 0) { //empty attribute
345+
if (isString(val) && val.length === 0) { //empty attribute
346346
element.data(NG_ANIMATE_CHILDREN, true);
347347
} else {
348348
scope.$watch(val, function(value) {
@@ -377,6 +377,7 @@ angular.module('ngAnimate', ['ng'])
377377
var forEach = angular.forEach;
378378
var selectors = $animateProvider.$$selectors;
379379
var isArray = angular.isArray;
380+
var isString = angular.isString;
380381

381382
var ELEMENT_NODE = 1;
382383
var NG_ANIMATE_STATE = '$$ngAnimateState';
@@ -467,6 +468,14 @@ angular.module('ngAnimate', ['ng'])
467468
return defer.promise;
468469
}
469470

471+
function parseAnimateOptions(options) {
472+
// some plugin code may still be passing in the callback
473+
// function as the last param for the $animate methods so
474+
// it's best to only allow string or array values for now
475+
if (isArray(options)) return options;
476+
if (isString(options)) return [options];
477+
}
478+
470479
function resolveElementClasses(element, cache, runningAnimations) {
471480
runningAnimations = runningAnimations || {};
472481

@@ -779,15 +788,16 @@ angular.module('ngAnimate', ['ng'])
779788
* @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation
780789
* @return {Promise} the animation callback promise
781790
*/
782-
enter : function(element, parentElement, afterElement) {
791+
enter : function(element, parentElement, afterElement, options) {
792+
options = parseAnimateOptions(options);
783793
element = angular.element(element);
784794
parentElement = prepareElement(parentElement);
785795
afterElement = prepareElement(afterElement);
786796

787797
classBasedAnimationsBlocked(element, true);
788798
$delegate.enter(element, parentElement, afterElement);
789799
return runAnimationPostDigest(function(done) {
790-
return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, done);
800+
return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done);
791801
});
792802
},
793803

@@ -821,7 +831,8 @@ angular.module('ngAnimate', ['ng'])
821831
* @param {DOMElement} element the element that will be the focus of the leave animation
822832
* @return {Promise} the animation callback promise
823833
*/
824-
leave : function(element) {
834+
leave : function(element, options) {
835+
options = parseAnimateOptions(options);
825836
element = angular.element(element);
826837

827838
cancelChildAnimations(element);
@@ -830,7 +841,7 @@ angular.module('ngAnimate', ['ng'])
830841
return runAnimationPostDigest(function(done) {
831842
return performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
832843
$delegate.leave(element);
833-
}, done);
844+
}, options, done);
834845
});
835846
},
836847

@@ -867,7 +878,8 @@ angular.module('ngAnimate', ['ng'])
867878
* @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation
868879
* @return {Promise} the animation callback promise
869880
*/
870-
move : function(element, parentElement, afterElement) {
881+
move : function(element, parentElement, afterElement, options) {
882+
options = parseAnimateOptions(options);
871883
element = angular.element(element);
872884
parentElement = prepareElement(parentElement);
873885
afterElement = prepareElement(afterElement);
@@ -876,7 +888,7 @@ angular.module('ngAnimate', ['ng'])
876888
classBasedAnimationsBlocked(element, true);
877889
$delegate.move(element, parentElement, afterElement);
878890
return runAnimationPostDigest(function(done) {
879-
return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, done);
891+
return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done);
880892
});
881893
},
882894

@@ -909,8 +921,8 @@ angular.module('ngAnimate', ['ng'])
909921
* @param {string} className the CSS class that will be added to the element and then animated
910922
* @return {Promise} the animation callback promise
911923
*/
912-
addClass : function(element, className) {
913-
return this.setClass(element, className, []);
924+
addClass : function(element, className, options) {
925+
return this.setClass(element, className, [], options);
914926
},
915927

916928
/**
@@ -942,8 +954,8 @@ angular.module('ngAnimate', ['ng'])
942954
* @param {string} className the CSS class that will be animated and then removed from the element
943955
* @return {Promise} the animation callback promise
944956
*/
945-
removeClass : function(element, className) {
946-
return this.setClass(element, [], className);
957+
removeClass : function(element, className, options) {
958+
return this.setClass(element, [], className, options);
947959
},
948960

949961
/**
@@ -973,7 +985,9 @@ angular.module('ngAnimate', ['ng'])
973985
* CSS classes have been set on the element
974986
* @return {Promise} the animation callback promise
975987
*/
976-
setClass : function(element, add, remove) {
988+
setClass : function(element, add, remove, options) {
989+
options = parseAnimateOptions(options);
990+
977991
var STORAGE_KEY = '$$animateClasses';
978992
element = angular.element(element);
979993
element = stripCommentsFromElement(element);
@@ -1007,11 +1021,16 @@ angular.module('ngAnimate', ['ng'])
10071021
});
10081022

10091023
if (hasCache) {
1024+
if (options && cache.options) {
1025+
cache.options = cache.options.concat(options);
1026+
}
1027+
10101028
//the digest cycle will combine all the animations into one function
10111029
return cache.promise;
10121030
} else {
10131031
element.data(STORAGE_KEY, cache = {
1014-
classes : classes
1032+
classes : classes,
1033+
options : options
10151034
});
10161035
}
10171036

@@ -1034,7 +1053,7 @@ angular.module('ngAnimate', ['ng'])
10341053
? done()
10351054
: performAnimation('setClass', classes, element, parentElement, null, function() {
10361055
$delegate.setClass(element, classes[0], classes[1]);
1037-
}, done);
1056+
}, cache.options, done);
10381057
});
10391058
},
10401059

@@ -1096,7 +1115,7 @@ angular.module('ngAnimate', ['ng'])
10961115
CSS code. Element, parentElement and afterElement are provided DOM elements for the animation
10971116
and the onComplete callback will be fired once the animation is fully complete.
10981117
*/
1099-
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
1118+
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, options, doneCallback) {
11001119

11011120
var noopCancel = noop;
11021121
var runner = animationRunner(element, animationEvent, className);
@@ -1204,6 +1223,11 @@ angular.module('ngAnimate', ['ng'])
12041223
//the ng-animate class does nothing, but it's here to allow for
12051224
//parent animations to find and cancel child animations when needed
12061225
element.addClass(NG_ANIMATE_CLASS_NAME);
1226+
if (isArray(options)) {
1227+
forEach(options, function(className) {
1228+
element.addClass(className);
1229+
});
1230+
}
12071231

12081232
var localAnimationCount = globalAnimationCounter++;
12091233
totalActiveAnimations++;
@@ -1273,8 +1297,15 @@ angular.module('ngAnimate', ['ng'])
12731297
function closeAnimation() {
12741298
if (!closeAnimation.hasBeenRun) {
12751299
closeAnimation.hasBeenRun = true;
1300+
if (isArray(options)) {
1301+
forEach(options, function(className) {
1302+
element.removeClass(className);
1303+
});
1304+
}
1305+
12761306
var data = element.data(NG_ANIMATE_STATE);
12771307
if (data) {
1308+
12781309
/* only structural animations wait for reflow before removing an
12791310
animation, but class-based animations don't. An example of this
12801311
failing would be when a parent HTML tag has a ng-class attribute
@@ -1539,7 +1570,7 @@ angular.module('ngAnimate', ['ng'])
15391570

15401571
function parseMaxTime(str) {
15411572
var maxValue = 0;
1542-
var values = angular.isString(str) ?
1573+
var values = isString(str) ?
15431574
str.split(/\s*,\s*/) :
15441575
[];
15451576
forEach(values, function(value) {

‎test/ngAnimate/animateSpec.js

+72
Original file line numberDiff line numberDiff line change
@@ -2450,6 +2450,78 @@ describe("ngAnimate", function() {
24502450
}));
24512451
});
24522452

2453+
describe("options", function() {
2454+
2455+
it('should add and remove the temporary className value is provided', function() {
2456+
var captures = {};
2457+
module(function($animateProvider) {
2458+
$animateProvider.register('.capture', function() {
2459+
return {
2460+
enter : capture('enter'),
2461+
leave : capture('leave'),
2462+
move : capture('move'),
2463+
addClass : capture('addClass'),
2464+
removeClass : capture('removeClass'),
2465+
setClass : capture('setClass')
2466+
};
2467+
2468+
function capture(event) {
2469+
return function(element, add, remove, done) {
2470+
//some animations only have one extra param
2471+
done = done || remove || add;
2472+
captures[event]=done;
2473+
};
2474+
}
2475+
});
2476+
});
2477+
inject(function($animate, $rootScope, $compile, $rootElement, $document) {
2478+
var container = jqLite('<div class="container"></div>');
2479+
var container2 = jqLite('<div class="container2"></div>');
2480+
var element = jqLite('<div class="capture"></div>');
2481+
$rootElement.append(container);
2482+
$rootElement.append(container2);
2483+
angular.element($document[0].body).append($rootElement);
2484+
2485+
$compile(element)($rootScope);
2486+
2487+
assertTempClass('enter', 'temp-enter', function() {
2488+
$animate.enter(element, container, null, 'temp-enter');
2489+
});
2490+
2491+
assertTempClass('move', 'temp-move', function() {
2492+
$animate.move(element, null, container2, 'temp-move');
2493+
});
2494+
2495+
assertTempClass('addClass', 'temp-add', function() {
2496+
$animate.addClass(element, 'add', 'temp-add');
2497+
});
2498+
2499+
assertTempClass('removeClass', 'temp-remove', function() {
2500+
$animate.removeClass(element, 'add', 'temp-remove');
2501+
});
2502+
2503+
element.addClass('remove');
2504+
assertTempClass('setClass', 'temp-set', function() {
2505+
$animate.setClass(element, 'add', 'remove', 'temp-set');
2506+
});
2507+
2508+
assertTempClass('leave', 'temp-leave', function() {
2509+
$animate.leave(element, 'temp-leave');
2510+
});
2511+
2512+
function assertTempClass(event, className, animationOperation) {
2513+
expect(element).not.toHaveClass(className);
2514+
animationOperation();
2515+
$rootScope.$digest();
2516+
expect(element).toHaveClass(className);
2517+
$animate.triggerReflow();
2518+
captures[event]();
2519+
$animate.triggerCallbacks();
2520+
expect(element).not.toHaveClass(className);
2521+
}
2522+
});
2523+
});
2524+
});
24532525

24542526
describe("addClass / removeClass", function() {
24552527

0 commit comments

Comments
 (0)
This repository has been archived.