diff --git a/components/core/config/config.ts b/components/core/config/config.ts index fa0414c29b2..5208d3ff22d 100644 --- a/components/core/config/config.ts +++ b/components/core/config/config.ts @@ -171,6 +171,7 @@ export interface MessageConfig { export interface ModalConfig { nzMask?: boolean; nzMaskClosable?: boolean; + nzCloseOnNavigation?: boolean; } export interface NotificationConfig extends MessageConfig { diff --git a/components/modal/doc/index.en-US.md b/components/modal/doc/index.en-US.md index b91e94f4866..b892ded166e 100644 --- a/components/modal/doc/index.en-US.md +++ b/components/modal/doc/index.en-US.md @@ -42,6 +42,7 @@ The dialog is currently divided into 2 modes, `normal mode` and `confirm box mod | nzKeyboard | Whether support press esc to close | `boolean` | `true` | | nzMask | Whether show mask or not. | `boolean` | `true` | ✅ | | nzMaskClosable | Whether to close the modal dialog when the mask (area outside the modal) is clicked | `boolean` | `true` | ✅ | +| nzCloseOnNavigation | Whether to close the modal when the navigation history changes | `boolean` | `true` | ✅ | | nzMaskStyle | Style for modal's mask element. | `object` | - | | nzOkText | Text of the OK button. Set to null to show no ok button (this value is invalid if the nzFooter parameter is used in normal mode) | `string` | OK | | nzOkType | Button type of the OK button. Consistent with the type of the `nz-button`. | `string` | primary | diff --git a/components/modal/doc/index.zh-CN.md b/components/modal/doc/index.zh-CN.md index 6dc9f9df570..0c1c49d4fb5 100644 --- a/components/modal/doc/index.zh-CN.md +++ b/components/modal/doc/index.zh-CN.md @@ -43,6 +43,7 @@ import { NzModalModule } from 'ng-zorro-antd/modal'; | nzKeyboard | 是否支持键盘esc关闭 | `boolean` | `true` | | nzMask | 是否展示遮罩 | `boolean` | `true` | ✅ | | nzMaskClosable | 点击蒙层是否允许关闭 | `boolean` | `true` | ✅ | +| nzCloseOnNavigation | 导航历史变化时是否关闭模态框 | `boolean` | `true` | ✅ | | nzMaskStyle | 遮罩样式 | `object` | - | | nzOkText | 确认按钮文字。设为 null 表示不显示确认按钮(若在普通模式下使用了 nzFooter 参数,则该值无效) | `string` | 确定 | | nzOkType | 确认按钮类型。与button的type类型值一致 | `string` | primary | diff --git a/components/modal/modal-container.ts b/components/modal/modal-container.ts index 5dbf93576c0..65ac8d31cd5 100644 --- a/components/modal/modal-container.ts +++ b/components/modal/modal-container.ts @@ -10,7 +10,7 @@ import { AnimationEvent } from '@angular/animations'; import { FocusTrap, FocusTrapFactory } from '@angular/cdk/a11y'; import { OverlayRef } from '@angular/cdk/overlay'; import { BasePortalOutlet, CdkPortalOutlet, ComponentPortal, TemplatePortal } from '@angular/cdk/portal'; -import { ChangeDetectorRef, ComponentRef, ElementRef, EmbeddedViewRef, EventEmitter, NgZone, Renderer2 } from '@angular/core'; +import { ChangeDetectorRef, ComponentRef, ElementRef, EmbeddedViewRef, EventEmitter, NgZone, OnDestroy, Renderer2 } from '@angular/core'; import { NzSafeAny } from 'ng-zorro-antd/core/types'; import { getElementOffset } from 'ng-zorro-antd/core/util'; import { FADE_CLASS_NAME_MAP, MODAL_MASK_CLASS_NAME, ZOOM_CLASS_NAME_MAP } from './modal-config'; @@ -22,7 +22,7 @@ export function throwNzModalContentAlreadyAttachedError(): never { throw Error('Attempting to attach modal content after content is already attached'); } -export class BaseModalContainer extends BasePortalOutlet { +export class BaseModalContainer extends BasePortalOutlet implements OnDestroy { portalOutlet!: CdkPortalOutlet; modalElementRef!: ElementRef; @@ -30,6 +30,7 @@ export class BaseModalContainer extends BasePortalOutlet { containerClick = new EventEmitter(); cancelTriggered = new EventEmitter(); okTriggered = new EventEmitter(); + onDestroy = new EventEmitter(); state: 'void' | 'enter' | 'exit' = 'enter'; document: Document; @@ -57,6 +58,10 @@ export class BaseModalContainer extends BasePortalOutlet { this.setContainer(); } + ngOnDestroy(): void { + this.onDestroy.emit(); + } + onMousedown(e: MouseEvent): void { this.latestMousedownTarget = (e.target as HTMLElement) || null; } diff --git a/components/modal/modal-ref.ts b/components/modal/modal-ref.ts index 98c5e5654a9..d5e0ab767f9 100644 --- a/components/modal/modal-ref.ts +++ b/components/modal/modal-ref.ts @@ -10,7 +10,7 @@ import { OverlayRef } from '@angular/cdk/overlay'; import { EventEmitter } from '@angular/core'; import { NzSafeAny } from 'ng-zorro-antd/core/types'; import { isPromise } from 'ng-zorro-antd/core/util'; -import { Subject } from 'rxjs'; +import { merge, Subject } from 'rxjs'; import { filter, take } from 'rxjs/operators'; import { BaseModalContainer } from './modal-container'; @@ -51,14 +51,17 @@ export class NzModalRef implements NzModalLegacyAP } }); - containerInstance.animationStateChanged - .pipe( + merge( + containerInstance.onDestroy, + containerInstance.animationStateChanged.pipe( filter(event => event.phaseName === 'done' && event.toState === 'exit'), take(1) ) + ) + .pipe(take(1)) .subscribe(() => { clearTimeout(this.closeTimeout); - this.overlayRef.dispose(); + this.finishDialogClose(); }); containerInstance.containerClick.pipe(take(1)).subscribe(() => { @@ -138,10 +141,9 @@ export class NzModalRef implements NzModalLegacyAP take(1) ) .subscribe(event => { - this.state = NzModalState.CLOSED; this.overlayRef.detachBackdrop(); this.closeTimeout = setTimeout(() => { - this.overlayRef.dispose(); + this.finishDialogClose(); }, event.totalTime + 100); }); @@ -196,4 +198,9 @@ export class NzModalRef implements NzModalLegacyAP this.close(result); } } + + private finishDialogClose(): void { + this.state = NzModalState.CLOSED; + this.overlayRef.dispose(); + } } diff --git a/components/modal/modal.component.ts b/components/modal/modal.component.ts index 881a0db4b5f..1a8ebc5d904 100644 --- a/components/modal/modal.component.ts +++ b/components/modal/modal.component.ts @@ -47,6 +47,7 @@ const NZ_CONFIG_COMPONENT_NAME = 'modal'; export class NzModalComponent implements OnChanges, NzModalLegacyAPI { static ngAcceptInputType_nzMask: BooleanInput; static ngAcceptInputType_nzMaskClosable: BooleanInput; + static ngAcceptInputType_nzCloseOnNavigation: BooleanInput; static ngAcceptInputType_nzVisible: BooleanInput; static ngAcceptInputType_nzClosable: BooleanInput; static ngAcceptInputType_nzOkLoading: BooleanInput; @@ -58,6 +59,7 @@ export class NzModalComponent implements OnChanges @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME) @InputBoolean() nzMask: boolean = true; @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME) @InputBoolean() nzMaskClosable: boolean = true; + @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME) @InputBoolean() nzCloseOnNavigation: boolean = true; @Input() @InputBoolean() nzVisible: boolean = false; @Input() @InputBoolean() nzClosable: boolean = true; @Input() @InputBoolean() nzOkLoading: boolean = false; diff --git a/components/modal/modal.spec.ts b/components/modal/modal.spec.ts index 22f308ab969..8177742af2a 100644 --- a/components/modal/modal.spec.ts +++ b/components/modal/modal.spec.ts @@ -1384,6 +1384,20 @@ describe('NzModal', () => { modalInstance.triggerCancel(); }).not.toThrowError(); })); + + it('should close when the host view is destroyed', fakeAsync(() => { + componentInstance.isVisible = true; + componentFixture.detectChanges(); + flush(); + + expect(overlayContainerElement.querySelector('nz-modal-container')).not.toBeNull(); + + componentFixture.destroy(); + componentFixture.detectChanges(); + flush(); + + expect(overlayContainerElement.querySelector('nz-modal-container')).toBeNull(); + })); }); }); diff --git a/components/modal/utils.ts b/components/modal/utils.ts index cff5ec55b5a..7550650343e 100644 --- a/components/modal/utils.ts +++ b/components/modal/utils.ts @@ -54,7 +54,8 @@ export function getConfigFromComponent(component: NzModalComponent): ModalOption nzOnOk, nzOnCancel, nzAfterOpen, - nzAfterClose + nzAfterClose, + nzCloseOnNavigation } = component; return { nzMask, @@ -87,6 +88,7 @@ export function getConfigFromComponent(component: NzModalComponent): ModalOption nzOnOk, nzOnCancel, nzAfterOpen, - nzAfterClose + nzAfterClose, + nzCloseOnNavigation }; }