From ea14ca870abb33d45514398af1981d5f56448c5b Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 10 Jan 2025 09:01:15 +0100 Subject: [PATCH] fix(material/menu): lazy content not detached after animation Fixes a regression after #30148 where we no longer detached the lazy content panel, causing its items to be retained until the next open when they get destroyed and re-created. --- src/material/menu/menu-trigger.ts | 6 +++++- src/material/menu/menu.spec.ts | 36 +++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/material/menu/menu-trigger.ts b/src/material/menu/menu-trigger.ts index e9fa1aa6cf17..338253c89667 100644 --- a/src/material/menu/menu-trigger.ts +++ b/src/material/menu/menu-trigger.ts @@ -368,10 +368,14 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy { // Note that we don't wait for the animation to finish if another trigger took // over the menu, because the panel will end up empty which looks glitchy. if (menu instanceof MatMenu && this._ownsMenu(menu)) { - this._pendingRemoval = menu._animationDone.pipe(take(1)).subscribe(() => overlayRef.detach()); + this._pendingRemoval = menu._animationDone.pipe(take(1)).subscribe(() => { + overlayRef.detach(); + menu.lazyContent?.detach(); + }); menu._setIsOpen(false); } else { overlayRef.detach(); + menu?.lazyContent?.detach(); } if (menu && this._ownsMenu(menu)) { diff --git a/src/material/menu/menu.spec.ts b/src/material/menu/menu.spec.ts index d25a6c9713b1..a065f4404038 100644 --- a/src/material/menu/menu.spec.ts +++ b/src/material/menu/menu.spec.ts @@ -15,9 +15,11 @@ import {ScrollDispatcher, ViewportRuler} from '@angular/cdk/scrolling'; import { ChangeDetectionStrategy, Component, + Directive, ElementRef, EventEmitter, Input, + OnDestroy, Output, Provider, QueryList, @@ -1219,6 +1221,40 @@ describe('MatMenu', () => { .toBe(true); })); + it('should detach the lazy content when the menu is closed', fakeAsync(() => { + let destroyCount = 0; + + // Note: for some reason doing `spyOn(item, 'ngOnDestroy')` doesn't work, even though a + // `console.log` shows that the `ngOnDestroy` gets called. We work around it with a custom + // directive that increments a counter. + @Directive({selector: '[mat-menu-item]', standalone: false}) + class DestroyChecker implements OnDestroy { + ngOnDestroy(): void { + destroyCount++; + } + } + + const fixture = createComponent(SimpleLazyMenu, undefined, [DestroyChecker]); + fixture.detectChanges(); + fixture.componentInstance.trigger.openMenu(); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + + expect(fixture.componentInstance.items.length).toBe(2); + expect(destroyCount).toBe(0); + + fixture.componentInstance.trigger.closeMenu(); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + + expect(fixture.componentInstance.items.length) + .withContext('Expected items to be removed from query list') + .toBe(0); + expect(destroyCount).withContext('Expected ngOnDestroy to have been called').toBe(2); + })); + it('should focus the first menu item when opening a lazy menu via keyboard', async () => { const fixture = createComponent(SimpleLazyMenu); fixture.autoDetectChanges();