Skip to content

Commit 34dce4f

Browse files
committed
fix(menu): not unsubscribing from close stream if trigger is destroyed
Fixes the menu trigger not unsubscribing from the menu panel's `close` stream. In most cases it isn't an issue, because `close` gets completed when the panel is destroyed, however the user can still run into it if the trigger is destroyed, but the panel stays in place.
1 parent 6d7f417 commit 34dce4f

File tree

1 file changed

+11
-14
lines changed

1 file changed

+11
-14
lines changed

src/lib/menu/menu-trigger.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import {
3434
ViewContainerRef,
3535
} from '@angular/core';
3636
import {normalizePassiveListenerOptions} from '@angular/cdk/platform';
37-
import {asapScheduler, merge, of as observableOf, Subscription} from 'rxjs';
37+
import {asapScheduler, merge, of as observableOf, Subscription, Subject} from 'rxjs';
3838
import {delay, filter, take, takeUntil} from 'rxjs/operators';
3939
import {MatMenu} from './menu-directive';
4040
import {throwMatMenuMissingError} from './menu-errors';
@@ -85,8 +85,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
8585
private _portal: TemplatePortal;
8686
private _overlayRef: OverlayRef | null = null;
8787
private _menuOpen: boolean = false;
88-
private _closeSubscription = Subscription.EMPTY;
89-
private _hoverSubscription = Subscription.EMPTY;
88+
private _destroyed = new Subject();
9089
private _menuCloseSubscription = Subscription.EMPTY;
9190
private _scrollStrategy: () => ScrollStrategy;
9291

@@ -194,7 +193,9 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
194193
this._element.nativeElement.removeEventListener('touchstart', this._handleTouchStart,
195194
passiveEventListenerOptions);
196195

197-
this._cleanUpSubscriptions();
196+
this._menuCloseSubscription.unsubscribe();
197+
this._destroyed.next();
198+
this._destroyed.complete();
198199
}
199200

200201
/** Whether the menu is open. */
@@ -233,7 +234,9 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
233234
this.menu.lazyContent.attach(this.menuData);
234235
}
235236

236-
this._closeSubscription = this._menuClosingActions().subscribe(() => this.closeMenu());
237+
this._menuClosingActions()
238+
.pipe(takeUntil(merge(this._destroyed, overlayRef.detachments())))
239+
.subscribe(() => this.closeMenu());
237240
this._initMenu();
238241

239242
if (this.menu instanceof MatMenu) {
@@ -266,7 +269,6 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
266269

267270
const menu = this.menu;
268271

269-
this._closeSubscription.unsubscribe();
270272
this._overlayRef.detach();
271273

272274
if (menu instanceof MatMenu) {
@@ -464,12 +466,6 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
464466
]);
465467
}
466468

467-
/** Cleans up the active subscriptions. */
468-
private _cleanUpSubscriptions(): void {
469-
this._closeSubscription.unsubscribe();
470-
this._hoverSubscription.unsubscribe();
471-
}
472-
473469
/** Returns a stream that emits whenever an action that should close the menu occurs. */
474470
private _menuClosingActions() {
475471
const backdrop = this._overlayRef!.backdropClick();
@@ -528,13 +524,14 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
528524
return;
529525
}
530526

531-
this._hoverSubscription = this._parentMenu._hovered()
527+
this._parentMenu._hovered()
532528
// Since we might have multiple competing triggers for the same menu (e.g. a sub-menu
533529
// with different data and triggers), we have to delay it by a tick to ensure that
534530
// it won't be closed immediately after it is opened.
535531
.pipe(
536532
filter(active => active === this._menuItemInstance && !active.disabled),
537-
delay(0, asapScheduler)
533+
delay(0, asapScheduler),
534+
takeUntil(this._destroyed)
538535
)
539536
.subscribe(() => {
540537
this._openedBy = 'mouse';

0 commit comments

Comments
 (0)