diff --git a/packages/core/src/render3/instructions/animation.ts b/packages/core/src/render3/instructions/animation.ts index 432d63726bb..815cfcfb759 100644 --- a/packages/core/src/render3/instructions/animation.ts +++ b/packages/core/src/render3/instructions/animation.ts @@ -319,15 +319,18 @@ function animateLeaveClassRunner( cleanupFns.push(renderer.listen(el, 'animationend', handleOutAnimationEnd)); cleanupFns.push(renderer.listen(el, 'transitionend', handleOutAnimationEnd)); }); + trackLeavingNodes(tNode, el); + for (const item of classList) { renderer.addClass(el, item); } - // In the case that the classes added have no animations, we need to remove - // the element right away. This could happen because someone is intentionally - // preventing an animation via selector specificity. + ngZone.runOutsideAngular(() => { requestAnimationFrame(() => { + // In the case that the classes added have no animations, we need to remove + // the element right away. This could happen because someone is intentionally + // preventing an animation via selector specificity. determineLongestAnimation(el, longestAnimations, areAnimationSupported); if (!longestAnimations.has(el)) { clearLeavingNodes(tNode, el); diff --git a/packages/core/test/acceptance/animation_spec.ts b/packages/core/test/acceptance/animation_spec.ts index 32761bd2010..41b1f76cf87 100644 --- a/packages/core/test/acceptance/animation_spec.ts +++ b/packages/core/test/acceptance/animation_spec.ts @@ -374,7 +374,6 @@ describe('Animation', () => { it('should be host bindable', fakeAsync(() => { @Component({ selector: 'fade-cmp', - styles: styles, host: {'animate.leave': 'fade'}, template: '
I should fade
', encapsulation: ViewEncapsulation.None, @@ -383,6 +382,7 @@ describe('Animation', () => { @Component({ selector: 'test-cmp', + styles: styles, imports: [FadeComponent], template: '@if (show()) {I should fade
', encapsulation: ViewEncapsulation.None, @@ -424,6 +424,7 @@ describe('Animation', () => { @Component({ selector: 'test-cmp', + styles: styles, imports: [FadeComponent], template: '@if (show()) {I should fade
', encapsulation: ViewEncapsulation.None, @@ -670,7 +671,6 @@ describe('Animation', () => { `; @Component({ selector: 'child-cmp', - styles: multiple, host: {'animate.leave': 'slide-out'}, template: 'I should fade
', encapsulation: ViewEncapsulation.None, @@ -1203,12 +1203,21 @@ describe('Animation', () => { it('should be host bindable', fakeAsync(() => { @Component({ - selector: 'test-cmp', - styles: styles, + selector: 'child-cmp', host: {'animate.enter': 'slide-in'}, template: 'I should fade
', encapsulation: ViewEncapsulation.None, }) + class ChildComponent {} + + @Component({ + selector: 'test-cmp', + styles: styles, + imports: [ChildComponent], + host: {'animate.enter': 'slide-in'}, + template: 'I should fade
', encapsulation: ViewEncapsulation.None, }) - class TestComponent { + class ChildComponent { slideIn = signal('slide-in'); } + + @Component({ + selector: 'test-cmp', + styles: styles, + imports: [ChildComponent], + template: 'I should fade
', encapsulation: ViewEncapsulation.None, }) + class ChildComponent { + slideIn(event: AnimationCallbackEvent) { + slideInCalled(); + event.target.classList.add('slide-in'); + event.animationComplete(); + } + } + + @Component({ + selector: 'test-cmp', + styles: styles, + imports: [ChildComponent], + template: 'I should fade
', encapsulation: ViewEncapsulation.None, @@ -1320,7 +1350,6 @@ describe('Animation', () => { it('should compose class list when host binding a string and regular class strings', fakeAsync(() => { @Component({ selector: 'child-cmp', - styles: styles, host: {'animate.enter': 'slide-in'}, template: 'I should fade
', encapsulation: ViewEncapsulation.None,