Skip to content

Commit 81bd671

Browse files
fix(core): prevent duplicate nodes from being retained with fast animate.leave` calls (#64592)
We were clearing duplicate nodes when `animate.enter` fired fast, but not when solely `animate.leave` is fired and rapid toggles occur. This ensures that the `cancelLeavingNodes` function is called in all cases instead of just enter animations. fixes: #64581 PR Close #64592
1 parent a3e91b2 commit 81bd671

File tree

2 files changed

+54
-2
lines changed

2 files changed

+54
-2
lines changed

packages/core/src/render3/instructions/animation.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ export function ɵɵanimateEnter(value: string | Function): typeof ɵɵanimateEn
6868
}
6969

7070
const tNode = getCurrentTNode()!;
71-
7271
cancelLeavingNodes(tNode, lView);
7372

7473
addAnimationToLView(getLViewEnterAnimations(lView), tNode, () =>
@@ -196,7 +195,6 @@ export function ɵɵanimateEnterListener(value: AnimationFunction): typeof ɵɵa
196195
return ɵɵanimateEnterListener;
197196
}
198197
const tNode = getCurrentTNode()!;
199-
200198
cancelLeavingNodes(tNode, lView);
201199

202200
addAnimationToLView(getLViewEnterAnimations(lView), tNode, () =>
@@ -251,6 +249,7 @@ export function ɵɵanimateLeave(value: string | Function): typeof ɵɵanimateLe
251249
}
252250

253251
const tNode = getCurrentTNode()!;
252+
cancelLeavingNodes(tNode, lView);
254253

255254
addAnimationToLView(getLViewLeaveAnimations(lView), tNode, () =>
256255
runLeaveAnimations(lView, tNode, value),
@@ -383,6 +382,8 @@ export function ɵɵanimateLeaveListener(value: AnimationFunction): typeof ɵɵa
383382

384383
const lView = getLView();
385384
const tNode = getCurrentTNode()!;
385+
cancelLeavingNodes(tNode, lView);
386+
386387
allLeavingAnimations.add(lView);
387388

388389
addAnimationToLView(getLViewLeaveAnimations(lView), tNode, () =>

packages/core/test/acceptance/animation_spec.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import {
1212
AfterViewInit,
1313
AnimationCallbackEvent,
1414
ChangeDetectionStrategy,
15+
ChangeDetectorRef,
1516
Component,
1617
computed,
1718
Directive,
1819
ElementRef,
20+
inject,
1921
NgModule,
2022
OnDestroy,
2123
provideZonelessChangeDetection,
@@ -1627,6 +1629,55 @@ describe('Animation', () => {
16271629
expect(paragraphs.length).toBe(1);
16281630
}));
16291631

1632+
it('should reset leave animation and not duplicate node when toggled programmatically very quickly', fakeAsync(() => {
1633+
const animateStyles = `
1634+
.fade {
1635+
animation: fade-out 500ms;
1636+
}
1637+
@keyframes fade-out {
1638+
from {
1639+
opacity: 1;
1640+
}
1641+
to {
1642+
opacity: 0;
1643+
}
1644+
}
1645+
`;
1646+
1647+
@Component({
1648+
selector: 'test-cmp',
1649+
styles: animateStyles,
1650+
template: '<div>@if (show()) {<p animate.leave="fade">I should fade</p>}</div>',
1651+
encapsulation: ViewEncapsulation.None,
1652+
})
1653+
class TestComponent {
1654+
show = signal(false);
1655+
cdr = inject(ChangeDetectorRef);
1656+
1657+
toggle() {
1658+
this.show.update((s) => !s);
1659+
setTimeout(() => {
1660+
this.show.update((s) => !s);
1661+
this.cdr.detectChanges();
1662+
1663+
setTimeout(() => {
1664+
this.show.update((s) => !s);
1665+
this.cdr.detectChanges();
1666+
});
1667+
});
1668+
}
1669+
}
1670+
TestBed.configureTestingModule({animationsEnabled: true});
1671+
1672+
const fixture = TestBed.createComponent(TestComponent);
1673+
const cmp = fixture.componentInstance;
1674+
cmp.toggle();
1675+
fixture.detectChanges();
1676+
tickAnimationFrames(1);
1677+
const paragraphs = fixture.debugElement.queryAll(By.css('p'));
1678+
expect(paragraphs.length).toBe(1);
1679+
}));
1680+
16301681
it('should always run animations for `@for` loops when adding and removing quickly', fakeAsync(() => {
16311682
const animateStyles = `
16321683
.slide-in {

0 commit comments

Comments
 (0)