mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(menu): detach lazily-rendered content when the menu is closed (#10005)
Currently the lazily-rendered content is maintained in the background while a menu is open. These changes switch to detaching it once the user closes the menu. Fixes #9915.
This commit is contained in:
committed by
tinayuangao
parent
67e710c81f
commit
bb1803dcc0
@@ -44,10 +44,10 @@ export class MatMenuContent implements OnDestroy {
|
||||
attach(context: any = {}) {
|
||||
if (!this._portal) {
|
||||
this._portal = new TemplatePortal(this._template, this._viewContainerRef);
|
||||
} else if (this._portal.isAttached) {
|
||||
this._portal.detach();
|
||||
}
|
||||
|
||||
this.detach();
|
||||
|
||||
if (!this._outlet) {
|
||||
this._outlet = new DomPortalOutlet(this._document.createElement('div'),
|
||||
this._componentFactoryResolver, this._appRef, this._injector);
|
||||
@@ -62,6 +62,16 @@ export class MatMenuContent implements OnDestroy {
|
||||
this._portal.attach(this._outlet, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches the content.
|
||||
* @docs-private
|
||||
*/
|
||||
detach() {
|
||||
if (this._portal.isAttached) {
|
||||
this._portal.detach();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this._outlet) {
|
||||
this._outlet.dispose();
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
import {merge} from 'rxjs/observable/merge';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
import {matMenuAnimations} from './menu-animations';
|
||||
@@ -103,6 +104,9 @@ export class MatMenu implements OnInit, AfterContentInit, MatMenuPanel, OnDestro
|
||||
/** Current state of the panel animation. */
|
||||
_panelAnimationState: 'void' | 'enter' = 'void';
|
||||
|
||||
/** Emits whenever an animation on the menu completes. */
|
||||
_animationDone = new Subject<void>();
|
||||
|
||||
/** Parent menu of the current menu panel. */
|
||||
parentMenu: MatMenuPanel | undefined;
|
||||
|
||||
@@ -324,7 +328,7 @@ export class MatMenu implements OnInit, AfterContentInit, MatMenuPanel, OnDestro
|
||||
}
|
||||
|
||||
/** Callback that is invoked when the panel animation completes. */
|
||||
_onAnimationDone(_event: AnimationEvent) {
|
||||
// @deletion-target 6.0.0 Not being used anymore. To be removed.
|
||||
_onAnimationDone() {
|
||||
this._animationDone.next();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from '@angular/cdk/overlay';
|
||||
import {TemplatePortal} from '@angular/cdk/portal';
|
||||
import {filter} from 'rxjs/operators/filter';
|
||||
import {take} from 'rxjs/operators/take';
|
||||
import {
|
||||
AfterContentInit,
|
||||
Directive,
|
||||
@@ -238,14 +239,27 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
|
||||
|
||||
/** Closes the menu and does the necessary cleanup. */
|
||||
private _destroyMenu() {
|
||||
if (this._overlayRef && this.menuOpen) {
|
||||
this._resetMenu();
|
||||
this._closeSubscription.unsubscribe();
|
||||
this._overlayRef.detach();
|
||||
if (!this._overlayRef || !this.menuOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.menu instanceof MatMenu) {
|
||||
this.menu._resetAnimation();
|
||||
const menu = this.menu;
|
||||
|
||||
this._resetMenu();
|
||||
this._closeSubscription.unsubscribe();
|
||||
this._overlayRef.detach();
|
||||
|
||||
if (menu instanceof MatMenu) {
|
||||
menu._resetAnimation();
|
||||
|
||||
if (menu.lazyContent) {
|
||||
// Wait for the exit animation to finish before detaching the content.
|
||||
menu._animationDone
|
||||
.pipe(take(1))
|
||||
.subscribe(() => menu.lazyContent!.detach());
|
||||
}
|
||||
} else if (menu.lazyContent) {
|
||||
menu.lazyContent.detach();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
(keydown)="_handleKeydown($event)"
|
||||
(click)="closed.emit('click')"
|
||||
[@transformMenu]="_panelAnimationState"
|
||||
(@transformMenu.done)="_onAnimationDone()"
|
||||
tabindex="-1"
|
||||
role="menu">
|
||||
<div class="mat-menu-content">
|
||||
|
||||
@@ -353,8 +353,8 @@ describe('MatMenu', () => {
|
||||
describe('lazy rendering', () => {
|
||||
it('should be able to render the menu content lazily', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(SimpleLazyMenu);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.componentInstance.triggerEl.nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
tick(500);
|
||||
@@ -366,6 +366,24 @@ describe('MatMenu', () => {
|
||||
expect(fixture.componentInstance.trigger.menuOpen).toBe(true, 'Expected menu to be open');
|
||||
}));
|
||||
|
||||
it('should detach the lazy content when the menu is closed', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(SimpleLazyMenu);
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.componentInstance.trigger.openMenu();
|
||||
fixture.detectChanges();
|
||||
tick(500);
|
||||
|
||||
expect(fixture.componentInstance.items.length).toBeGreaterThan(0);
|
||||
|
||||
fixture.componentInstance.trigger.closeMenu();
|
||||
fixture.detectChanges();
|
||||
tick(500);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.items.length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should focus the first menu item when opening a lazy menu via keyboard', fakeAsync(() => {
|
||||
let zone: MockNgZone;
|
||||
|
||||
@@ -398,8 +416,8 @@ describe('MatMenu', () => {
|
||||
|
||||
it('should be able to open the same menu with a different context', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(LazyMenuWithContext);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.componentInstance.triggerOne.openMenu();
|
||||
fixture.detectChanges();
|
||||
tick(500);
|
||||
@@ -1589,6 +1607,7 @@ class FakeIcon {}
|
||||
class SimpleLazyMenu {
|
||||
@ViewChild(MatMenuTrigger) trigger: MatMenuTrigger;
|
||||
@ViewChild('triggerEl') triggerEl: ElementRef;
|
||||
@ViewChildren(MatMenuItem) items: QueryList<MatMenuItem>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user