diff --git a/components/dropdown/doc/index.en-US.md b/components/dropdown/doc/index.en-US.md index a0ac5dc28a4..33c872a9360 100755 --- a/components/dropdown/doc/index.en-US.md +++ b/components/dropdown/doc/index.en-US.md @@ -26,6 +26,7 @@ If there are too many operations to display, you can wrap them in a `Dropdown`. | `[nzOverlayStyle]` | Style of the dropdown root element | `object` | - | | `(nzVisibleChange)` | a callback function takes an argument: `nzVisible`, is executed when the visible state is changed | `EventEmitter` | - | | `[nzArrow]` | Whether the dropdown arrow should be visible | `boolean` | `false` | 20.2.0 | +| `[nzDestroyOnHidden]` | Whether destroy dropdown when hidden | `boolean` | `false` | 21.0.0 | You should use [nz-menu](/components/menu/en) in `nz-dropdown`. The menu items and dividers are also available by using `nz-menu-item` and `nz-menu-divider`. diff --git a/components/dropdown/doc/index.zh-CN.md b/components/dropdown/doc/index.zh-CN.md index 8487594651f..2163b440572 100755 --- a/components/dropdown/doc/index.zh-CN.md +++ b/components/dropdown/doc/index.zh-CN.md @@ -27,6 +27,7 @@ description: 向下弹出的列表。 | `[nzOverlayStyle]` | 下拉根元素的样式 | `object` | - | | `(nzVisibleChange)` | 菜单显示状态改变时调用,参数为 nzVisible | `EventEmitter` | - | | `[nzArrow]` | 下拉框箭头是否显示 | `boolean` | `false` | 20.2.0 | +| `[nzDestroyOnHidden]` | 关闭后是否销毁 Dropdown | `boolean` | `false` | 21.0.0 | 菜单使用 [nz-menu](/components/menu/zh),还包括菜单项 `[nz-menu-item]`,分割线 `[nz-menu-divider]`。 diff --git a/components/dropdown/dropdown.directive.spec.ts b/components/dropdown/dropdown.directive.spec.ts index 0db1f1d531d..39c9eda4861 100644 --- a/components/dropdown/dropdown.directive.spec.ts +++ b/components/dropdown/dropdown.directive.spec.ts @@ -5,6 +5,7 @@ import { ESCAPE } from '@angular/cdk/keycodes'; import { OverlayContainer } from '@angular/cdk/overlay'; +import { TemplatePortal } from '@angular/cdk/portal'; import { Component } from '@angular/core'; import { ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; @@ -211,6 +212,60 @@ describe('dropdown', () => { }).not.toThrowError(); })); + describe('should nzDestroyOnHidden work', () => { + it('should not recreate portal when nzDestroyOnHidden is false', fakeAsync(() => { + const fixture = TestBed.createComponent(NzTestDropdownComponent); + fixture.componentInstance.destroyOnHidden = false; + fixture.detectChanges(); + expect(() => { + const dropdownElement = fixture.debugElement.query(By.directive(NzDropdownDirective)).nativeElement; + dispatchFakeEvent(dropdownElement, 'mouseenter'); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + const portal_pre = fixture.debugElement + .query(By.directive(NzDropdownDirective)) + .injector.get(NzDropdownDirective)['portal']; + expect(portal_pre instanceof TemplatePortal).toBe(true); + dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); + tick(1000); + fixture.detectChanges(); + dispatchFakeEvent(dropdownElement, 'mouseenter'); + tick(1000); + fixture.detectChanges(); + expect( + fixture.debugElement.query(By.directive(NzDropdownDirective)).injector.get(NzDropdownDirective)['portal'] + ).toBe(portal_pre); + }).not.toThrowError(); + })); + + it('should recreate portal when nzDestroyOnHidden is true', fakeAsync(() => { + const fixture = TestBed.createComponent(NzTestDropdownComponent); + fixture.componentInstance.destroyOnHidden = true; + fixture.detectChanges(); + expect(() => { + const dropdownElement = fixture.debugElement.query(By.directive(NzDropdownDirective)).nativeElement; + dispatchFakeEvent(dropdownElement, 'mouseenter'); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + const portal_pre = fixture.debugElement + .query(By.directive(NzDropdownDirective)) + .injector.get(NzDropdownDirective)['portal']; + expect(portal_pre instanceof TemplatePortal).toBeTruthy(); + dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); + tick(1000); + fixture.detectChanges(); + dispatchFakeEvent(dropdownElement, 'mouseenter'); + tick(1000); + fixture.detectChanges(); + expect( + fixture.debugElement.query(By.directive(NzDropdownDirective)).injector.get(NzDropdownDirective)['portal'] + ).not.toBe(portal_pre); + }).not.toThrowError(); + })); + }); + it('should nzVisible & nzClickHide work', fakeAsync(() => { const fixture = TestBed.createComponent(NzTestDropdownVisibleComponent); fixture.detectChanges(); @@ -256,6 +311,7 @@ describe('dropdown', () => { [nzBackdrop]="backdrop" [nzOverlayClassName]="className" [nzOverlayStyle]="overlayStyle" + [nzDestroyOnHidden]="destroyOnHidden" > Trigger @@ -275,6 +331,7 @@ export class NzTestDropdownComponent { disabled = false; className = 'custom-class'; overlayStyle = { color: '#000' }; + destroyOnHidden = false; } @Component({ diff --git a/components/dropdown/dropdown.directive.ts b/components/dropdown/dropdown.directive.ts index d13e7ad5252..acd089a6ed9 100644 --- a/components/dropdown/dropdown.directive.ts +++ b/components/dropdown/dropdown.directive.ts @@ -93,6 +93,8 @@ export class NzDropdownDirective implements AfterViewInit, OnChanges { @Input() nzOverlayClassName: string = ''; @Input() nzOverlayStyle: IndexableObject = {}; @Input() nzPlacement: NzPlacementType = 'bottomLeft'; + // Whether destroy dropdown when hidden + @Input() nzDestroyOnHidden: boolean = false; @Output() readonly nzVisibleChange = new EventEmitter(); constructor() { @@ -229,6 +231,9 @@ export class NzDropdownDirective implements AfterViewInit, OnChanges { if (event.toState === 'void') { this.overlayRef?.dispose(); this.overlayRef = null; + if (this.nzDestroyOnHidden) { + this.portal = undefined; + } } }); }