@@ -38,6 +38,7 @@ import {tickAnimationFrames} from '../animation_utils/tick_animation_frames';
3838import { BrowserTestingModule , platformBrowserTesting } from '@angular/platform-browser/testing' ;
3939import { NoopAnimationsModule } from '@angular/platform-browser/animations' ;
4040import { ComponentRef } from '@angular/core/src/render3' ;
41+ import { reusedNodes } from '../../src/animation/utils' ;
4142
4243@NgModule ( {
4344 providers : [ provideZonelessChangeDetection ( ) ] ,
@@ -511,6 +512,60 @@ describe('Animation', () => {
511512 expect ( fadeCalled ) . toHaveBeenCalled ( ) ;
512513 } ) ) ;
513514
515+ it ( 'should remove element from DOM with (animate.leave) after list reordering' , async ( ) => {
516+ @Component ( {
517+ selector : 'leak-cmp' ,
518+ template : 'item' ,
519+ } )
520+ class LeakComponent { }
521+
522+ @Component ( {
523+ selector : 'test-cmp' ,
524+ imports : [ LeakComponent ] ,
525+ template : `
526+ @for (item of items(); track item.id) {
527+ <leak-cmp (animate.leave)="onLeave($event)" />
528+ }
529+ ` ,
530+ } )
531+ class TestComponent {
532+ items = signal ( [ { id : '1' } , { id : '2' } ] ) ;
533+
534+ onLeave ( event : AnimationCallbackEvent ) {
535+ event . animationComplete ( ) ;
536+ }
537+
538+ shuffle ( ) {
539+ const arr = this . items ( ) ;
540+ this . items . set ( [ arr [ 1 ] , arr [ 0 ] ] ) ;
541+ }
542+
543+ remove ( ) {
544+ const arr = this . items ( ) ;
545+ this . items . set ( [ arr [ 1 ] ] ) ;
546+ }
547+ }
548+
549+ TestBed . configureTestingModule ( { animationsEnabled : true } ) ;
550+ const fixture = TestBed . createComponent ( TestComponent ) ;
551+ await fixture . whenStable ( ) ;
552+
553+ expect ( fixture . nativeElement . textContent ) . toContain ( 'item' ) ;
554+
555+ fixture . componentInstance . shuffle ( ) ;
556+ await fixture . whenStable ( ) ;
557+
558+ const elementsBeforeRemove = fixture . debugElement . queryAll ( By . css ( 'leak-cmp' ) ) ;
559+ // Element is in reused nodes before remove
560+ expect ( reusedNodes . has ( elementsBeforeRemove [ 0 ] . nativeElement ) ) . toBe ( true ) ;
561+
562+ fixture . componentInstance . remove ( ) ;
563+ await fixture . whenStable ( ) ;
564+
565+ const elements = fixture . nativeElement . querySelectorAll ( 'leak-cmp' ) ;
566+ expect ( elements . length ) . toBe ( 1 ) ;
567+ } ) ;
568+
514569 it ( 'should compose class list when host binding and regular binding' , fakeAsync ( ( ) => {
515570 const multiple = `
516571 .slide-out {
0 commit comments