diff --git a/components/dropdown/context-menu.service.spec.ts b/components/dropdown/context-menu.service.spec.ts index efa630b821..e45b27ac56 100644 --- a/components/dropdown/context-menu.service.spec.ts +++ b/components/dropdown/context-menu.service.spec.ts @@ -1,5 +1,5 @@ import { OverlayContainer, ScrollDispatcher } from '@angular/cdk/overlay'; -import { Component, Provider, Type, ViewChild } from '@angular/core'; +import { ApplicationRef, Component, Provider, Type, ViewChild } from '@angular/core'; import { ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { Subject } from 'rxjs'; @@ -119,6 +119,22 @@ describe('context-menu', () => { expect(overlayContainerElement.textContent).toBe(''); }).not.toThrowError(); })); + it('should not run change detection if the overlay is clicked inside', async () => { + const fixture = createComponent(NzTestDropdownContextMenuComponent, [], []); + fixture.detectChanges(); + const fakeEvent = createMouseEvent('contextmenu', 300, 300); + const component = fixture.componentInstance; + component.nzContextMenuService.create(fakeEvent, component.nzDropdownMenuComponent); + fixture.detectChanges(); + await fixture.whenStable(); + fixture.detectChanges(); + const appRef = TestBed.inject(ApplicationRef); + spyOn(appRef, 'tick'); + overlayContainerElement.querySelector('ul')!.click(); + expect(appRef.tick).toHaveBeenCalledTimes(0); + document.body.click(); + expect(appRef.tick).toHaveBeenCalledTimes(1); + }); }); @Component({ diff --git a/components/dropdown/context-menu.service.ts b/components/dropdown/context-menu.service.ts index 89217013cc..8a5083d551 100644 --- a/components/dropdown/context-menu.service.ts +++ b/components/dropdown/context-menu.service.ts @@ -5,8 +5,8 @@ import { ConnectionPositionPair, Overlay, OverlayRef } from '@angular/cdk/overlay'; import { TemplatePortal } from '@angular/cdk/portal'; -import { Injectable } from '@angular/core'; -import { fromEvent, merge, Subscription } from 'rxjs'; +import { Injectable, NgZone } from '@angular/core'; +import { fromEvent, Subscription } from 'rxjs'; import { filter, take } from 'rxjs/operators'; import { NzContextMenuServiceModule } from './context-menu.service.module'; @@ -26,7 +26,7 @@ export class NzContextMenuService { private overlayRef: OverlayRef | null = null; private closeSubscription = Subscription.EMPTY; - constructor(private overlay: Overlay) {} + constructor(private ngZone: NgZone, private overlay: Overlay) {} create($event: MouseEvent | { x: number; y: number }, nzDropdownMenuComponent: NzDropdownMenuComponent): void { this.close(true); @@ -44,17 +44,24 @@ export class NzContextMenuService { disposeOnNavigation: true, scrollStrategy: this.overlay.scrollStrategies.close() }); - this.closeSubscription = merge( - nzDropdownMenuComponent.descendantMenuItemClick$, - fromEvent(document, 'click').pipe( - filter(event => !!this.overlayRef && !this.overlayRef.overlayElement.contains(event.target as HTMLElement)), - /** handle firefox contextmenu event **/ - filter(event => event.button !== 2), - take(1) + + this.closeSubscription = new Subscription(); + + this.closeSubscription.add(nzDropdownMenuComponent.descendantMenuItemClick$.subscribe(() => this.close())); + + this.closeSubscription.add( + this.ngZone.runOutsideAngular(() => + fromEvent(document, 'click') + .pipe( + filter(event => !!this.overlayRef && !this.overlayRef.overlayElement.contains(event.target as HTMLElement)), + /** handle firefox contextmenu event **/ + filter(event => event.button !== 2), + take(1) + ) + .subscribe(() => this.ngZone.run(() => this.close())) ) - ).subscribe(() => { - this.close(); - }); + ); + this.overlayRef.attach( new TemplatePortal(nzDropdownMenuComponent.templateRef, nzDropdownMenuComponent.viewContainerRef) );