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

Commit 958d3d5

Browse files
committed
fix($animate): ensure animations work with directives that share a transclusion
Closes #4716 Closes #4871 Closes #5021 Closes #5278
1 parent 0e50810 commit 958d3d5

File tree

2 files changed

+95
-19
lines changed

2 files changed

+95
-19
lines changed

src/ngAnimate/animate.js

+42-19
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,19 @@ angular.module('ngAnimate', ['ng'])
258258
var NG_ANIMATE_CLASS_NAME = 'ng-animate';
259259
var rootAnimateState = {running: true};
260260

261+
function extractElementNode(element) {
262+
for(var i = 0; i < element.length; i++) {
263+
var elm = element[i];
264+
if(elm.nodeType == ELEMENT_NODE) {
265+
return elm;
266+
}
267+
}
268+
}
269+
270+
function isMatchingElement(elm1, elm2) {
271+
return extractElementNode(elm1) == extractElementNode(elm2);
272+
}
273+
261274
$provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope', '$document',
262275
function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope, $document) {
263276

@@ -556,7 +569,16 @@ angular.module('ngAnimate', ['ng'])
556569
and the onComplete callback will be fired once the animation is fully complete.
557570
*/
558571
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
559-
var currentClassName = element.attr('class') || '';
572+
var node = extractElementNode(element);
573+
//transcluded directives may sometimes fire an animation using only comment nodes
574+
//best to catch this early on to prevent any animation operations from occurring
575+
if(!node) {
576+
fireDOMOperation();
577+
closeAnimation();
578+
return;
579+
}
580+
581+
var currentClassName = node.className;
560582
var classes = currentClassName + ' ' + className;
561583
var animationLookup = (' ' + classes).replace(/\s+/g,'.');
562584
if (!parentElement) {
@@ -760,11 +782,7 @@ angular.module('ngAnimate', ['ng'])
760782
}
761783

762784
function cancelChildAnimations(element) {
763-
var node = element[0];
764-
if(node.nodeType != ELEMENT_NODE) {
765-
return;
766-
}
767-
785+
var node = extractElementNode(element);
768786
forEach(node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME), function(element) {
769787
element = angular.element(element);
770788
var data = element.data(NG_ANIMATE_STATE);
@@ -788,7 +806,7 @@ angular.module('ngAnimate', ['ng'])
788806
}
789807

790808
function cleanup(element) {
791-
if(element[0] == $rootElement[0]) {
809+
if(isMatchingElement(element, $rootElement)) {
792810
if(!rootAnimateState.disabled) {
793811
rootAnimateState.running = false;
794812
rootAnimateState.structural = false;
@@ -802,7 +820,7 @@ angular.module('ngAnimate', ['ng'])
802820
function animationsDisabled(element, parentElement) {
803821
if (rootAnimateState.disabled) return true;
804822

805-
if(element[0] == $rootElement[0]) {
823+
if(isMatchingElement(element, $rootElement)) {
806824
return rootAnimateState.disabled || rootAnimateState.running;
807825
}
808826

@@ -812,7 +830,7 @@ angular.module('ngAnimate', ['ng'])
812830
//any animations on it
813831
if(parentElement.length === 0) break;
814832

815-
var isRoot = parentElement[0] == $rootElement[0];
833+
var isRoot = isMatchingElement(parentElement, $rootElement);
816834
var state = isRoot ? rootAnimateState : parentElement.data(NG_ANIMATE_STATE);
817835
var result = state && (!!state.disabled || !!state.running);
818836
if(isRoot || result) {
@@ -960,7 +978,7 @@ angular.module('ngAnimate', ['ng'])
960978
parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
961979
parentID = parentCounter;
962980
}
963-
return parentID + '-' + element[0].className;
981+
return parentID + '-' + extractElementNode(element).className;
964982
}
965983

966984
function animateSetup(element, className) {
@@ -995,7 +1013,6 @@ angular.module('ngAnimate', ['ng'])
9951013
return false;
9961014
}
9971015

998-
var node = element[0];
9991016
//temporarily disable the transition so that the enter styles
10001017
//don't animate twice (this is here to avoid a bug in Chrome/FF).
10011018
var activeClassName = '';
@@ -1025,35 +1042,37 @@ angular.module('ngAnimate', ['ng'])
10251042
}
10261043

10271044
function blockTransitions(element) {
1028-
element[0].style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
1045+
extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
10291046
}
10301047

10311048
function blockKeyframeAnimations(element) {
1032-
element[0].style[ANIMATION_PROP] = 'none 0s';
1049+
extractElementNode(element).style[ANIMATION_PROP] = 'none 0s';
10331050
}
10341051

10351052
function unblockTransitions(element) {
1036-
var node = element[0], prop = TRANSITION_PROP + PROPERTY_KEY;
1053+
var prop = TRANSITION_PROP + PROPERTY_KEY;
1054+
var node = extractElementNode(element);
10371055
if(node.style[prop] && node.style[prop].length > 0) {
10381056
node.style[prop] = '';
10391057
}
10401058
}
10411059

10421060
function unblockKeyframeAnimations(element) {
1043-
var node = element[0], prop = ANIMATION_PROP;
1061+
var prop = ANIMATION_PROP;
1062+
var node = extractElementNode(element);
10441063
if(node.style[prop] && node.style[prop].length > 0) {
1045-
element[0].style[prop] = '';
1064+
node.style[prop] = '';
10461065
}
10471066
}
10481067

10491068
function animateRun(element, className, activeAnimationComplete) {
10501069
var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
1051-
if(!element.hasClass(className) || !data) {
1070+
var node = extractElementNode(element);
1071+
if(node.className.indexOf(className) == -1 || !data) {
10521072
activeAnimationComplete();
10531073
return;
10541074
}
10551075

1056-
var node = element[0];
10571076
var timings = data.timings;
10581077
var stagger = data.stagger;
10591078
var maxDuration = data.maxDuration;
@@ -1096,6 +1115,9 @@ angular.module('ngAnimate', ['ng'])
10961115
}
10971116

10981117
if(appliedStyles.length > 0) {
1118+
//the element being animated may sometimes contain comment nodes in
1119+
//the jqLite object, so we're safe to use a single variable to house
1120+
//the styles since there is always only one element being animated
10991121
var oldStyle = node.getAttribute('style') || '';
11001122
node.setAttribute('style', oldStyle + ' ' + style);
11011123
}
@@ -1110,6 +1132,7 @@ angular.module('ngAnimate', ['ng'])
11101132
element.off(css3AnimationEvents, onAnimationProgress);
11111133
element.removeClass(activeClassName);
11121134
animateClose(element, className);
1135+
var node = extractElementNode(element);
11131136
for (var i in appliedStyles) {
11141137
node.style.removeProperty(appliedStyles[i]);
11151138
}
@@ -1209,7 +1232,7 @@ angular.module('ngAnimate', ['ng'])
12091232
}
12101233

12111234
var parentElement = element.parent();
1212-
var clone = angular.element(element[0].cloneNode());
1235+
var clone = angular.element(extractElementNode(element).cloneNode());
12131236

12141237
//make the element super hidden and override any CSS style values
12151238
clone.attr('style','position:absolute; top:-9999px; left:-9999px');

test/ngAnimate/animateSpec.js

+53
Original file line numberDiff line numberDiff line change
@@ -2873,5 +2873,58 @@ describe("ngAnimate", function() {
28732873

28742874
expect($rootElement.children().length).toBe(0);
28752875
}));
2876+
2877+
it('should properly animate elements with compound directives', function() {
2878+
var capturedAnimation;
2879+
module(function($animateProvider) {
2880+
$animateProvider.register('.special', function() {
2881+
return {
2882+
enter : function(element, done) {
2883+
capturedAnimation = 'enter';
2884+
done();
2885+
},
2886+
leave : function(element, done) {
2887+
capturedAnimation = 'leave';
2888+
done();
2889+
}
2890+
}
2891+
});
2892+
});
2893+
inject(function($rootScope, $compile, $rootElement, $document, $timeout, $templateCache, $sniffer) {
2894+
if(!$sniffer.transitions) return;
2895+
2896+
$templateCache.put('item-template', 'item: #{{ item }} ');
2897+
var element = $compile('<div>' +
2898+
' <div ng-repeat="item in items"' +
2899+
' ng-include="tpl"' +
2900+
' class="special"></div>' +
2901+
'</div>')($rootScope);
2902+
2903+
ss.addRule('.special', '-webkit-transition:1s linear all;' +
2904+
'transition:1s linear all;');
2905+
2906+
$rootElement.append(element);
2907+
jqLite($document[0].body).append($rootElement);
2908+
2909+
$rootScope.tpl = 'item-template';
2910+
$rootScope.items = [1,2,3];
2911+
$rootScope.$digest();
2912+
$timeout.flush();
2913+
2914+
expect(capturedAnimation).toBe('enter');
2915+
expect(element.text()).toContain('item: #1');
2916+
2917+
forEach(element.children(), function(kid) {
2918+
browserTrigger(kid, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
2919+
});
2920+
$timeout.flush();
2921+
2922+
$rootScope.items = [];
2923+
$rootScope.$digest();
2924+
$timeout.flush();
2925+
2926+
expect(capturedAnimation).toBe('leave');
2927+
});
2928+
});
28762929
});
28772930
});

0 commit comments

Comments
 (0)