fix(animations): do not leak DOM nodes/styling for host triggered animations (#18853)

Closes #18606

PR Close #18853
This commit is contained in:
Matias Niemelä 2017-08-23 15:32:26 -07:00 committed by Jason Aden
parent fd701b07f0
commit fcadeb2079
2 changed files with 55 additions and 8 deletions

View File

@ -837,7 +837,7 @@ export class TransitionAnimationEngine {
} }
const allLeaveNodes: any[] = []; const allLeaveNodes: any[] = [];
const leaveNodesWithoutAnimations: any[] = []; const leaveNodesWithoutAnimations = new Set<any>();
for (let i = 0; i < this.collectedLeaveElements.length; i++) { for (let i = 0; i < this.collectedLeaveElements.length; i++) {
const element = this.collectedLeaveElements[i]; const element = this.collectedLeaveElements[i];
const details = element[REMOVAL_FLAG] as ElementAnimationState; const details = element[REMOVAL_FLAG] as ElementAnimationState;
@ -845,7 +845,7 @@ export class TransitionAnimationEngine {
addClass(element, LEAVE_CLASSNAME); addClass(element, LEAVE_CLASSNAME);
allLeaveNodes.push(element); allLeaveNodes.push(element);
if (!details.hasAnimation) { 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 // these can only be detected here since we have a map of all the elements
// that have animations attached to them... // that have animations attached to them... We use a set here in the event
const enterNodesWithoutAnimations: any[] = []; // 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<any>();
for (let i = 0; i < allEnterNodes.length; i++) { for (let i = 0; i < allEnterNodes.length; i++) {
const element = allEnterNodes[i]; const element = allEnterNodes[i];
if (!subTimelines.has(element)) { if (!subTimelines.has(element)) {
enterNodesWithoutAnimations.push(element); enterNodesWithoutAnimations.add(element);
} }
} }
@ -1435,9 +1437,11 @@ function cloakElement(element: any, value?: string) {
} }
function cloakAndComputeStyles( function cloakAndComputeStyles(
driver: AnimationDriver, elements: any[], elementPropsMap: Map<any, Set<string>>, driver: AnimationDriver, elements: Set<any>, elementPropsMap: Map<any, Set<string>>,
defaultStyle: string): [Map<any, ɵStyleData>, any[]] { defaultStyle: string): [Map<any, ɵStyleData>, any[]] {
const cloakVals = elements.map(element => cloakElement(element)); const cloakVals: string[] = [];
elements.forEach(element => cloakVals.push(cloakElement(element)));
const valuesMap = new Map<any, ɵStyleData>(); const valuesMap = new Map<any, ɵStyleData>();
const failedElements: any[] = []; const failedElements: any[] = [];
@ -1456,7 +1460,10 @@ function cloakAndComputeStyles(
valuesMap.set(element, styles); 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]; return [valuesMap, failedElements];
} }

View File

@ -733,6 +733,46 @@ export function main() {
flushMicrotasks(); flushMicrotasks();
expect(fixture.debugElement.nativeElement.children.length).toBe(0); 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: `
<child-cmp *ngFor="let item of items"></child-cmp>
`
})
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', it('should cancel and merge in mid-animation styles into the follow-up animation, but only for animation keyframes that start right away',