diff --git a/packages/animations/browser/src/render/transition_animation_engine.ts b/packages/animations/browser/src/render/transition_animation_engine.ts index 50b6e581a5..fe779f3bb8 100644 --- a/packages/animations/browser/src/render/transition_animation_engine.ts +++ b/packages/animations/browser/src/render/transition_animation_engine.ts @@ -837,7 +837,7 @@ export class TransitionAnimationEngine { } const allLeaveNodes: any[] = []; - const leaveNodesWithoutAnimations: any[] = []; + const leaveNodesWithoutAnimations = new Set(); for (let i = 0; i < this.collectedLeaveElements.length; i++) { const element = this.collectedLeaveElements[i]; const details = element[REMOVAL_FLAG] as ElementAnimationState; @@ -845,7 +845,7 @@ export class TransitionAnimationEngine { addClass(element, LEAVE_CLASSNAME); allLeaveNodes.push(element); if (!details.hasAnimation) { - leaveNodesWithoutAnimations.push(element); + leaveNodesWithoutAnimations.add(element); } } } @@ -937,12 +937,14 @@ export class TransitionAnimationEngine { } // these can only be detected here since we have a map of all the elements - // that have animations attached to them... - const enterNodesWithoutAnimations: any[] = []; + // that have animations attached to them... We use a set here in the event + // multiple enter captures on the same element were caught in different + // renderer namespaces (e.g. when a @trigger was on a host binding that had *ngIf) + const enterNodesWithoutAnimations = new Set(); for (let i = 0; i < allEnterNodes.length; i++) { const element = allEnterNodes[i]; if (!subTimelines.has(element)) { - enterNodesWithoutAnimations.push(element); + enterNodesWithoutAnimations.add(element); } } @@ -1435,9 +1437,11 @@ function cloakElement(element: any, value?: string) { } function cloakAndComputeStyles( - driver: AnimationDriver, elements: any[], elementPropsMap: Map>, + driver: AnimationDriver, elements: Set, elementPropsMap: Map>, defaultStyle: string): [Map, any[]] { - const cloakVals = elements.map(element => cloakElement(element)); + const cloakVals: string[] = []; + elements.forEach(element => cloakVals.push(cloakElement(element))); + const valuesMap = new Map(); const failedElements: any[] = []; @@ -1456,7 +1460,10 @@ function cloakAndComputeStyles( valuesMap.set(element, styles); }); - elements.forEach((element, i) => cloakElement(element, cloakVals[i])); + // we use a index variable here since Set.forEach(a, i) does not return + // an index value for the closure (but instead just the value) + let i = 0; + elements.forEach(element => cloakElement(element, cloakVals[i++])); return [valuesMap, failedElements]; } diff --git a/packages/core/test/animation/animation_integration_spec.ts b/packages/core/test/animation/animation_integration_spec.ts index d387886fec..a8471f9179 100644 --- a/packages/core/test/animation/animation_integration_spec.ts +++ b/packages/core/test/animation/animation_integration_spec.ts @@ -733,6 +733,46 @@ export function main() { flushMicrotasks(); expect(fixture.debugElement.nativeElement.children.length).toBe(0); })); + + it('should properly evaluate pre/auto-style values when components are inserted/removed which contain host animations', + fakeAsync(() => { + @Component({ + selector: 'parent-cmp', + template: ` + + ` + }) + class ParentCmp { + items: any[] = [1, 2, 3, 4, 5]; + } + + @Component({ + selector: 'child-cmp', + template: '... child ...', + animations: + [trigger('host', [transition(':leave', [animate(1000, style({opacity: 0}))])])] + }) + class ChildCmp { + @HostBinding('@host') public hostAnimation = 'a'; + } + + TestBed.configureTestingModule({declarations: [ParentCmp, ChildCmp]}); + + const engine = TestBed.get(ɵAnimationEngine); + const fixture = TestBed.createComponent(ParentCmp); + const cmp = fixture.componentInstance; + const element = fixture.nativeElement; + fixture.detectChanges(); + + cmp.items = [0, 2, 4, 6]; // 1,3,5 get removed + fixture.detectChanges(); + + const items = element.querySelectorAll('child-cmp'); + for (let i = 0; i < items.length; i++) { + const item = items[i]; + expect(item.style['display']).toBeFalsy(); + } + })); }); it('should cancel and merge in mid-animation styles into the follow-up animation, but only for animation keyframes that start right away',