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:
Kristiyan Kostadinov
2018-03-02 23:27:07 +02:00
committed by tinayuangao
parent 67e710c81f
commit bb1803dcc0
5 changed files with 60 additions and 12 deletions
+12 -2
View File
@@ -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();
+6 -2
View File
@@ -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();
}
}
+20 -6
View File
@@ -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();
}
}
+1
View File
@@ -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">
+21 -2
View File
@@ -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>;
}