@@ -13,13 +13,24 @@ import {AnimationTransitionInstruction} from '../dsl/animation_transition_instru
1313import { AnimationTrigger } from '../dsl/animation_trigger' ;
1414import { ElementInstructionMap } from '../dsl/element_instruction_map' ;
1515import { AnimationStyleNormalizer } from '../dsl/style_normalization/animation_style_normalizer' ;
16- import { ENTER_CLASSNAME , LEAVE_CLASSNAME , LEAVE_SELECTOR , NG_ANIMATING_CLASSNAME , NG_TRIGGER_CLASSNAME , NG_TRIGGER_SELECTOR , copyObj , eraseStyles , setStyles } from '../util' ;
16+ import { ENTER_CLASSNAME , LEAVE_CLASSNAME , NG_ANIMATING_CLASSNAME , NG_TRIGGER_CLASSNAME , NG_TRIGGER_SELECTOR , copyObj , eraseStyles , setStyles } from '../util' ;
1717
1818import { AnimationDriver } from './animation_driver' ;
1919import { getOrSetAsInMap , listenOnPlayer , makeAnimationEvent , normalizeKeyframes , optimizeGroupPlayer } from './shared' ;
2020
2121const EMPTY_PLAYER_ARRAY : AnimationPlayer [ ] = [ ] ;
22- const NOOP_FN = ( ) => { } ;
22+ const NULL_REMOVAL_STATE : ElementAnimationState = {
23+ namespaceId : '' ,
24+ setForRemoval : null ,
25+ hasAnimation : false ,
26+ removedBeforeQueried : false
27+ } ;
28+ const NULL_REMOVED_QUERIED_STATE : ElementAnimationState = {
29+ namespaceId : '' ,
30+ setForRemoval : null ,
31+ hasAnimation : false ,
32+ removedBeforeQueried : true
33+ } ;
2334
2435interface TriggerListener {
2536 name : string ;
@@ -37,6 +48,15 @@ export interface QueueInstruction {
3748 isFallbackTransition : boolean ;
3849}
3950
51+ export const REMOVAL_FLAG = '__ng_removed' ;
52+
53+ export interface ElementAnimationState {
54+ setForRemoval : any ;
55+ hasAnimation : boolean ;
56+ namespaceId : string ;
57+ removedBeforeQueried : boolean ;
58+ }
59+
4060export class StateValue {
4161 public value : string ;
4262 public options : AnimationOptions ;
@@ -245,7 +265,7 @@ export class AnimationTransitionNamespace {
245265 } ) ;
246266 }
247267
248- private _onElementDestroy ( element : any ) {
268+ clearElementCache ( element : any ) {
249269 this . _engine . statesByElement . delete ( element ) ;
250270 this . _elementListeners . delete ( element ) ;
251271 const elementPlayers = this . _engine . playersByElement . get ( element ) ;
@@ -267,14 +287,13 @@ export class AnimationTransitionNamespace {
267287
268288 this . removeNode ( elm , context , true ) ;
269289 } else {
270- this . _onElementDestroy ( elm ) ;
290+ this . clearElementCache ( elm ) ;
271291 }
272292 } ) ;
273293 }
274294
275295 removeNode ( element : any , context : any , doNotRecurse ?: boolean ) : void {
276296 const engine = this . _engine ;
277- engine . markElementAsRemoved ( element ) ;
278297
279298 if ( ! doNotRecurse && element . childElementCount ) {
280299 this . _destroyInnerNodes ( element , context , true ) ;
@@ -295,12 +314,8 @@ export class AnimationTransitionNamespace {
295314 } ) ;
296315
297316 if ( players . length ) {
298- optimizeGroupPlayer ( players ) . onDone ( ( ) => {
299- engine . destroyInnerAnimations ( element ) ;
300- this . _onElementDestroy ( element ) ;
301- engine . _onRemovalComplete ( element , context ) ;
302- } ) ;
303-
317+ engine . markElementAsRemoved ( this . id , element , true , context ) ;
318+ optimizeGroupPlayer ( players ) . onDone ( ( ) => engine . processLeaveNode ( element ) ) ;
304319 return ;
305320 }
306321 }
@@ -365,15 +380,11 @@ export class AnimationTransitionNamespace {
365380 // whether or not a parent has an animation we need to delay the deferral of the leave
366381 // operation until we have more information (which we do after flush() has been called)
367382 if ( containsPotentialParentTransition ) {
368- engine . queuedRemovals . set ( element , ( ) => {
369- engine . destroyInnerAnimations ( element ) ;
370- this . _onElementDestroy ( element ) ;
371- engine . _onRemovalComplete ( element , context ) ;
372- } ) ;
383+ engine . markElementAsRemoved ( this . id , element , false , context ) ;
373384 } else {
374385 // we do this after the flush has occurred such
375386 // that the callbacks can be fired
376- engine . afterFlush ( ( ) => this . _onElementDestroy ( element ) ) ;
387+ engine . afterFlush ( ( ) => this . clearElementCache ( element ) ) ;
377388 engine . destroyInnerAnimations ( element ) ;
378389 engine . _onRemovalComplete ( element , context ) ;
379390 }
@@ -447,7 +458,6 @@ export interface QueuedTransition {
447458
448459export class TransitionAnimationEngine {
449460 public players : TransitionAnimationPlayer [ ] = [ ] ;
450- public queuedRemovals = new Map < any , ( ) => any > ( ) ;
451461 public newHostElements = new Map < any , AnimationTransitionNamespace > ( ) ;
452462 public playersByElement = new Map < any , TransitionAnimationPlayer [ ] > ( ) ;
453463 public playersByQueriedElement = new Map < any , TransitionAnimationPlayer [ ] > ( ) ;
@@ -462,6 +472,7 @@ export class TransitionAnimationEngine {
462472
463473 public namespacesByHostElement = new Map < any , AnimationTransitionNamespace > ( ) ;
464474 public collectedEnterElements : any [ ] = [ ] ;
475+ public collectedLeaveElements : any [ ] = [ ] ;
465476
466477 // this method is designed to be overridden by the code that uses this engine
467478 public onRemovalComplete = ( element : any , context : any ) => { } ;
@@ -572,8 +583,9 @@ export class TransitionAnimationEngine {
572583
573584 // special case for when an element is removed and reinserted (move operation)
574585 // when this occurs we do not want to use the element for deletion later
575- if ( this . queuedRemovals . has ( element ) ) {
576- this . queuedRemovals . delete ( element ) ;
586+ const details = element [ REMOVAL_FLAG ] as ElementAnimationState ;
587+ if ( details && details . setForRemoval ) {
588+ details . setForRemoval = false ;
577589 }
578590
579591 // in the event that the namespaceId is blank then the caller
@@ -592,19 +604,27 @@ export class TransitionAnimationEngine {
592604 collectEnterElement ( element : any ) { this . collectedEnterElements . push ( element ) ; }
593605
594606 removeNode ( namespaceId : string , element : any , context : any , doNotRecurse ?: boolean ) : void {
595- if ( namespaceId ) {
596- const ns = this . _fetchNamespace ( namespaceId ) ;
597- if ( ! isElementNode ( element ) || ! ns ) {
598- this . _onRemovalComplete ( element , context ) ;
599- } else {
600- ns . removeNode ( element , context , doNotRecurse ) ;
601- }
607+ if ( ! isElementNode ( element ) ) {
608+ this . _onRemovalComplete ( element , context ) ;
609+ return ;
610+ }
611+
612+ const ns = namespaceId ? this . _fetchNamespace ( namespaceId ) : null ;
613+ if ( ns ) {
614+ ns . removeNode ( element , context , doNotRecurse ) ;
602615 } else {
603- this . queuedRemovals . set ( element , ( ) => this . _onRemovalComplete ( element , context ) ) ;
616+ this . markElementAsRemoved ( namespaceId , element , false , context ) ;
604617 }
605618 }
606619
607- markElementAsRemoved ( element : any ) { this . queuedRemovals . set ( element , NOOP_FN ) ; }
620+ markElementAsRemoved ( namespaceId : string , element : any , hasAnimation ?: boolean , context ?: any ) {
621+ this . collectedLeaveElements . push ( element ) ;
622+ element [ REMOVAL_FLAG ] = {
623+ namespaceId,
624+ setForRemoval : context , hasAnimation,
625+ removedBeforeQueried : false
626+ } ;
627+ }
608628
609629 listen (
610630 namespaceId : string , element : any , name : string , phase : string ,
@@ -653,22 +673,42 @@ export class TransitionAnimationEngine {
653673 } ) ;
654674 }
655675
676+ processLeaveNode ( element : any ) {
677+ const details = element [ REMOVAL_FLAG ] as ElementAnimationState ;
678+ if ( details && details . setForRemoval ) {
679+ // this will prevent it from removing it twice
680+ element [ REMOVAL_FLAG ] = NULL_REMOVAL_STATE ;
681+ if ( details . namespaceId ) {
682+ this . destroyInnerAnimations ( element ) ;
683+ const ns = this . _fetchNamespace ( details . namespaceId ) ;
684+ if ( ns ) {
685+ ns . clearElementCache ( element ) ;
686+ }
687+ }
688+ this . _onRemovalComplete ( element , details . setForRemoval ) ;
689+ }
690+ }
691+
656692 flush ( microtaskId : number = - 1 ) {
657693 let players : AnimationPlayer [ ] = [ ] ;
658694 if ( this . newHostElements . size ) {
659695 this . newHostElements . forEach ( ( ns , element ) => this . _balanceNamespaceList ( ns , element ) ) ;
660696 this . newHostElements . clear ( ) ;
661697 }
662698
663- if ( this . _namespaceList . length && ( this . totalQueuedPlayers || this . queuedRemovals . size ) ) {
699+ if ( this . _namespaceList . length &&
700+ ( this . totalQueuedPlayers || this . collectedLeaveElements . length ) ) {
664701 players = this . _flushAnimations ( microtaskId ) ;
665702 } else {
666- this . queuedRemovals . forEach ( fn => fn ( ) ) ;
703+ for ( let i = 0 ; i < this . collectedLeaveElements . length ; i ++ ) {
704+ const element = this . collectedLeaveElements [ i ] ;
705+ this . processLeaveNode ( element ) ;
706+ }
667707 }
668708
669709 this . totalQueuedPlayers = 0 ;
670710 this . collectedEnterElements . length = 0 ;
671- this . queuedRemovals . clear ( ) ;
711+ this . collectedLeaveElements . length = 0 ;
672712 this . _flushFns . forEach ( fn => fn ( ) ) ;
673713 this . _flushFns = [ ] ;
674714
@@ -704,7 +744,17 @@ export class TransitionAnimationEngine {
704744 const enterNodes : any [ ] =
705745 allEnterNodes . length ? collectEnterElements ( this . driver , allEnterNodes ) : [ ] ;
706746
707- this . queuedRemovals . forEach ( ( fn , element ) => addClass ( element , LEAVE_CLASSNAME ) ) ;
747+ const leaveNodes : any [ ] = [ ] ;
748+ for ( let i = 0 ; i < this . collectedLeaveElements . length ; i ++ ) {
749+ const element = this . collectedLeaveElements [ i ] ;
750+ if ( isElementNode ( element ) ) {
751+ const details = element [ REMOVAL_FLAG ] as ElementAnimationState ;
752+ if ( details && details . setForRemoval ) {
753+ addClass ( element , LEAVE_CLASSNAME ) ;
754+ leaveNodes . push ( element ) ;
755+ }
756+ }
757+ }
708758
709759 for ( let i = this . _namespaceList . length - 1 ; i >= 0 ; i -- ) {
710760 const ns = this . _namespaceList [ i ] ;
@@ -788,10 +838,6 @@ export class TransitionAnimationEngine {
788838
789839 allPreviousPlayersMap . forEach ( players => players . forEach ( player => player . destroy ( ) ) ) ;
790840
791- const leaveNodes : any [ ] = bodyNode && allPostStyleElements . size ?
792- this . driver . query ( bodyNode , LEAVE_SELECTOR , true ) :
793- [ ] ;
794-
795841 // PRE STAGE: fill the ! styles
796842 const preStylesMap = allPreStyleElements . size ?
797843 cloakAndComputeStyles ( this . driver , enterNodes , allPreStyleElements , PRE_STYLE ) :
@@ -861,14 +907,18 @@ export class TransitionAnimationEngine {
861907 // run through all of the queued removals and see if they
862908 // were picked up by a query. If not then perform the removal
863909 // operation right away unless a parent animation is ongoing.
864- this . queuedRemovals . forEach ( ( fn , element ) => {
910+ for ( let i = 0 ; i < leaveNodes . length ; i ++ ) {
911+ const element = leaveNodes [ i ] ;
865912 const players = queriedElements . get ( element ) ;
866913 if ( players ) {
867- optimizeGroupPlayer ( players ) . onDone ( fn ) ;
914+ removeNodesAfterAnimationDone ( this , element , players ) ;
868915 } else {
869- fn ( ) ;
916+ const details = element [ REMOVAL_FLAG ] as ElementAnimationState ;
917+ if ( details && ! details . hasAnimation ) {
918+ this . processLeaveNode ( element ) ;
919+ }
870920 }
871- } ) ;
921+ }
872922
873923 rootPlayers . forEach ( player => {
874924 this . players . push ( player ) ;
@@ -888,7 +938,8 @@ export class TransitionAnimationEngine {
888938
889939 elementContainsData ( namespaceId : string , element : any ) {
890940 let containsData = false ;
891- if ( this . queuedRemovals . has ( element ) ) containsData = true ;
941+ const details = element [ REMOVAL_FLAG ] as ElementAnimationState ;
942+ if ( details && details . setForRemoval ) containsData = true ;
892943 if ( this . playersByElement . has ( element ) ) containsData = true ;
893944 if ( this . playersByQueriedElement . has ( element ) ) containsData = true ;
894945 if ( this . statesByElement . has ( element ) ) containsData = true ;
@@ -979,7 +1030,8 @@ export class TransitionAnimationEngine {
9791030 const element = timelineInstruction . element ;
9801031
9811032 // FIXME (matsko): make sure to-be-removed animations are removed properly
982- if ( element [ 'REMOVED' ] ) return new NoopAnimationPlayer ( ) ;
1033+ const details = element [ REMOVAL_FLAG ] ;
1034+ if ( details && details . removedBeforeQueried ) return new NoopAnimationPlayer ( ) ;
9831035
9841036 const isQueriedElement = element !== rootElement ;
9851037 let previousPlayers : AnimationPlayer [ ] = EMPTY_PLAYER_ARRAY ;
@@ -1229,7 +1281,7 @@ function cloakAndComputeStyles(
12291281 // there is no easy way to detect this because a sub element could be removed
12301282 // by a parent animation element being detached.
12311283 if ( ! value || value . length == 0 ) {
1232- element [ 'REMOVED' ] = true ;
1284+ element [ REMOVAL_FLAG ] = NULL_REMOVED_QUERIED_STATE ;
12331285 }
12341286 } ) ;
12351287 valuesMap . set ( element , styles ) ;
@@ -1280,17 +1332,14 @@ function removeClass(element: any, className: string) {
12801332 }
12811333}
12821334
1283- function setAttribute ( element : any , attr : string , value : any ) {
1284- if ( element . setAttribute ) {
1285- element . setAttribute ( attr , value ) ;
1286- } else {
1287- element [ attr ] = value ;
1288- }
1289- }
1290-
12911335function getBodyNode ( ) : any | null {
12921336 if ( typeof document != 'undefined' ) {
12931337 return document . body ;
12941338 }
12951339 return null ;
12961340}
1341+
1342+ function removeNodesAfterAnimationDone (
1343+ engine : TransitionAnimationEngine , element : any , players : AnimationPlayer [ ] ) {
1344+ optimizeGroupPlayer ( players ) . onDone ( ( ) => engine . processLeaveNode ( element ) ) ;
1345+ }
0 commit comments