diff --git a/components/popconfirm/demo/async.md b/components/popconfirm/demo/async.md
new file mode 100755
index 0000000000..1e32334fd1
--- /dev/null
+++ b/components/popconfirm/demo/async.md
@@ -0,0 +1,16 @@
+---
+order: 6
+title:
+ zh-CN: 异步关闭
+ en-US: Asynchronously close
+---
+
+## zh-CN
+
+点击确定后异步关闭 Popconfirm,例如提交表单。
+
+## en-US
+
+Asynchronously close a popconfirm when a the OK button is pressed. For example, you can use this pattern when you submit a form.
+
+
diff --git a/components/popconfirm/demo/async.ts b/components/popconfirm/demo/async.ts
new file mode 100644
index 0000000000..0b88bf3eaa
--- /dev/null
+++ b/components/popconfirm/demo/async.ts
@@ -0,0 +1,41 @@
+import { Component } from '@angular/core';
+import { Observable } from 'rxjs';
+
+import { NzMessageService } from 'ng-zorro-antd/message';
+
+@Component({
+ selector: 'nz-demo-popconfirm-async',
+ template: `
+
+ `
+})
+export class NzDemoPopconfirmAsyncComponent {
+ cancel(): void {
+ this.nzMessageService.info('click cancel');
+ }
+
+ confirm(): void {
+ this.nzMessageService.info('click confirm');
+ }
+
+ beforeConfirm(): Observable {
+ return new Observable(observer => {
+ setTimeout(() => {
+ observer.next(true);
+ observer.complete();
+ }, 3000);
+ });
+ }
+
+ constructor(private nzMessageService: NzMessageService) {}
+}
diff --git a/components/popconfirm/demo/promise.md b/components/popconfirm/demo/promise.md
new file mode 100755
index 0000000000..5e54a74b95
--- /dev/null
+++ b/components/popconfirm/demo/promise.md
@@ -0,0 +1,16 @@
+---
+order: 7
+title:
+ zh-CN: 基于 Promise 的异步关闭
+ en-US: Asynchronously close on Promise
+---
+
+## zh-CN
+
+点击确定后异步关闭 Popconfirm,例如提交表单。
+
+## en-US
+
+Asynchronously close a popconfirm when the OK button is pressed. For example, you can use this pattern when you submit a form.
+
+
diff --git a/components/popconfirm/demo/promise.ts b/components/popconfirm/demo/promise.ts
new file mode 100644
index 0000000000..24fc130db4
--- /dev/null
+++ b/components/popconfirm/demo/promise.ts
@@ -0,0 +1,39 @@
+import { Component } from '@angular/core';
+
+import { NzMessageService } from 'ng-zorro-antd/message';
+
+@Component({
+ selector: 'nz-demo-popconfirm-promise',
+ template: `
+
+ `
+})
+export class NzDemoPopconfirmPromiseComponent {
+ cancel(): void {
+ this.nzMessageService.info('click cancel');
+ }
+
+ confirm(): void {
+ this.nzMessageService.info('click confirm');
+ }
+
+ beforeConfirm(): Promise {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ resolve(true);
+ }, 3000);
+ });
+ }
+
+ constructor(private nzMessageService: NzMessageService) {}
+}
diff --git a/components/popconfirm/doc/index.en-US.md b/components/popconfirm/doc/index.en-US.md
index d92944721d..feec99d0a2 100644
--- a/components/popconfirm/doc/index.en-US.md
+++ b/components/popconfirm/doc/index.en-US.md
@@ -46,6 +46,7 @@ import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';
| `[nzCondition]` | Whether to directly emit `onConfirm` without showing Popconfirm | `boolean` | `false` | - |
| `[nzIcon]` | Customize icon of confirmation | `string \| TemplateRef` | - | - |
| `[nzAutoFocus]` | Autofocus a button | `null \| 'ok' \| 'cancel'` | `null` | ✅ |
+| `[nzBeforeConfirm]` | The hook before the confirmation operation, decides whether to continue responding to the `nzOnConfirm` callback, supports asynchronous verification. | `(() => Observable \| Promise \| boolean) \| null` | `null` | - |
| `(nzOnCancel)` | Callback of cancel | `EventEmitter` | - | - |
| `(nzOnConfirm)` | Callback of confirmation | `EventEmitter` | - | - |
diff --git a/components/popconfirm/doc/index.zh-CN.md b/components/popconfirm/doc/index.zh-CN.md
index 1c20c08d01..d20bb041d4 100644
--- a/components/popconfirm/doc/index.zh-CN.md
+++ b/components/popconfirm/doc/index.zh-CN.md
@@ -50,6 +50,7 @@ import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';
| `[nzCondition]` | 是否直接触发 `nzOnConfirm` 而不弹出框 | `boolean` | `false` | - |
| `[nzIcon]` | 自定义弹出框的 icon | `string \| TemplateRef` | - | - |
| `[nzAutoFocus]` | 按钮的自动聚焦 | `null \| 'ok' \| 'cancel'` | `null` | ✅ |
+| `[nzBeforeConfirm]` | 确认操作之前的钩子,决定是否继续响应 `nzOnConfirm` 回调,支持异步验证。 | `(() => Observable \| Promise \| boolean) \| null` | `null` | - |
| `(nzOnCancel)` | 点击取消的回调 | `EventEmitter` | - | - |
| `(nzOnConfirm)` | 点击确认的回调 | `EventEmitter` | - | - |
diff --git a/components/popconfirm/popconfirm.spec.ts b/components/popconfirm/popconfirm.spec.ts
index 04796f8b52..d27f596d9a 100644
--- a/components/popconfirm/popconfirm.spec.ts
+++ b/components/popconfirm/popconfirm.spec.ts
@@ -3,6 +3,7 @@ import { Component, ElementRef, ViewChild } from '@angular/core';
import { ComponentFixture, fakeAsync, inject, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { Observable } from 'rxjs';
import { NzButtonType } from 'ng-zorro-antd/button';
import { dispatchMouseEvent } from 'ng-zorro-antd/core/testing';
@@ -144,6 +145,74 @@ describe('NzPopconfirm', () => {
expect(component.cancel).toHaveBeenCalledTimes(0);
}));
+ it('should before confirm work', fakeAsync(() => {
+ const triggerElement = component.stringTemplate.nativeElement;
+
+ dispatchMouseEvent(triggerElement, 'click');
+ fixture.detectChanges();
+ expect(getTitleText()!.textContent).toContain('title-string');
+ expect(component.confirm).toHaveBeenCalledTimes(0);
+ expect(component.cancel).toHaveBeenCalledTimes(0);
+
+ component.beforeConfirm = () => false;
+ fixture.detectChanges();
+
+ dispatchMouseEvent(getTooltipTrigger(1), 'click');
+ waitingForTooltipToggling();
+ expect(component.confirm).toHaveBeenCalledTimes(0);
+ expect(component.cancel).toHaveBeenCalledTimes(0);
+ expect(getTitleText()!.textContent).toContain('title-string');
+ }));
+
+ it('should before confirm observable work', fakeAsync(() => {
+ const triggerElement = component.stringTemplate.nativeElement;
+
+ dispatchMouseEvent(triggerElement, 'click');
+ fixture.detectChanges();
+ expect(getTitleText()!.textContent).toContain('title-string');
+ expect(component.confirm).toHaveBeenCalledTimes(0);
+ expect(component.cancel).toHaveBeenCalledTimes(0);
+
+ component.beforeConfirm = () =>
+ new Observable(observer => {
+ setTimeout(() => {
+ observer.next(true);
+ observer.complete();
+ }, 200);
+ });
+
+ dispatchMouseEvent(getTooltipTrigger(1), 'click');
+ tick(200 + 10);
+ waitingForTooltipToggling();
+ expect(getTitleText()).toBeNull();
+ expect(component.confirm).toHaveBeenCalledTimes(1);
+ expect(component.cancel).toHaveBeenCalledTimes(0);
+ }));
+
+ it('should before confirm promise work', fakeAsync(() => {
+ const triggerElement = component.stringTemplate.nativeElement;
+
+ dispatchMouseEvent(triggerElement, 'click');
+ fixture.detectChanges();
+ expect(getTitleText()!.textContent).toContain('title-string');
+ expect(component.confirm).toHaveBeenCalledTimes(0);
+ expect(component.cancel).toHaveBeenCalledTimes(0);
+
+ component.beforeConfirm = () =>
+ new Promise(resolve => {
+ setTimeout(() => {
+ resolve(true);
+ }, 200);
+ });
+
+ dispatchMouseEvent(getTooltipTrigger(1), 'click');
+ tick(200 + 10);
+ waitingForTooltipToggling();
+ expect(getTitleText()).toBeNull();
+ expect(component.confirm).toHaveBeenCalledTimes(1);
+ expect(component.cancel).toHaveBeenCalledTimes(0);
+ }));
+
it('should nzPopconfirmShowArrow work', fakeAsync(() => {
const triggerElement = component.stringTemplate.nativeElement;
dispatchMouseEvent(triggerElement, 'click');
@@ -178,6 +247,7 @@ describe('NzPopconfirm', () => {
nzCancelText="cancel-text"
[nzAutofocus]="autoFocus"
[nzCondition]="condition"
+ [nzBeforeConfirm]="beforeConfirm"
[nzPopconfirmShowArrow]="nzPopconfirmShowArrow"
[nzPopconfirmBackdrop]="nzPopconfirmBackdrop"
(nzOnConfirm)="confirm()"
@@ -210,6 +280,7 @@ export class NzPopconfirmTestNewComponent {
icon: string | undefined = undefined;
nzPopconfirmBackdrop = false;
autoFocus: NzAutoFocusType = null;
+ beforeConfirm: (() => Observable | Promise | boolean) | null = null;
@ViewChild('stringTemplate', { static: false }) stringTemplate!: ElementRef;
@ViewChild('templateTemplate', { static: false }) templateTemplate!: ElementRef;
diff --git a/components/popconfirm/popconfirm.ts b/components/popconfirm/popconfirm.ts
index aaf8ecca52..f93215dbd4 100644
--- a/components/popconfirm/popconfirm.ts
+++ b/components/popconfirm/popconfirm.ts
@@ -27,15 +27,15 @@ import {
ViewContainerRef,
ViewEncapsulation
} from '@angular/core';
-import { Subject } from 'rxjs';
-import { takeUntil } from 'rxjs/operators';
+import { Observable, Subject } from 'rxjs';
+import { finalize, first, takeUntil } from 'rxjs/operators';
import { NzButtonType } from 'ng-zorro-antd/button';
import { zoomBigMotion } from 'ng-zorro-antd/core/animation';
import { NzConfigKey, NzConfigService, WithConfig } from 'ng-zorro-antd/core/config';
import { NzNoAnimationDirective } from 'ng-zorro-antd/core/no-animation';
import { BooleanInput, NgStyleInterface, NzSafeAny, NzTSType } from 'ng-zorro-antd/core/types';
-import { InputBoolean } from 'ng-zorro-antd/core/util';
+import { InputBoolean, wrapIntoObservable } from 'ng-zorro-antd/core/util';
import { NzTooltipBaseDirective, NzToolTipComponent, NzTooltipTrigger, PropertyMapping } from 'ng-zorro-antd/tooltip';
export type NzAutoFocusType = null | 'ok' | 'cancel';
@@ -70,6 +70,7 @@ export class NzPopconfirmDirective extends NzTooltipBaseDirective {
@Input() nzOkType?: string;
@Input() nzOkDanger?: boolean;
@Input() nzCancelText?: string;
+ @Input() nzBeforeConfirm?: () => Observable | Promise | boolean;
@Input() nzIcon?: string | TemplateRef;
@Input() @InputBoolean() nzCondition: boolean = false;
@Input() @InputBoolean() nzPopconfirmShowArrow: boolean = true;
@@ -90,6 +91,7 @@ export class NzPopconfirmDirective extends NzTooltipBaseDirective {
nzOkType: ['nzOkType', () => this.nzOkType],
nzOkDanger: ['nzOkDanger', () => this.nzOkDanger],
nzCancelText: ['nzCancelText', () => this.nzCancelText],
+ nzBeforeConfirm: ['nzBeforeConfirm', () => this.nzBeforeConfirm],
nzCondition: ['nzCondition', () => this.nzCondition],
nzIcon: ['nzIcon', () => this.nzIcon],
nzPopconfirmShowArrow: ['nzPopconfirmShowArrow', () => this.nzPopconfirmShowArrow],
@@ -190,6 +192,7 @@ export class NzPopconfirmDirective extends NzTooltipBaseDirective {
[nzSize]="'small'"
[nzType]="nzOkType !== 'danger' ? nzOkType : 'primary'"
[nzDanger]="nzOkDanger || nzOkType === 'danger'"
+ [nzLoading]="confirmLoading"
(click)="onConfirm()"
[attr.cdkFocusInitial]="nzAutoFocus === 'ok' || null"
>
@@ -217,6 +220,7 @@ export class NzPopconfirmComponent extends NzToolTipComponent implements OnDestr
nzOkType: NzButtonType | 'danger' = 'primary';
nzOkDanger: boolean = false;
nzAutoFocus: NzAutoFocusType = null;
+ nzBeforeConfirm: (() => Observable | Promise | boolean) | null = null;
readonly nzOnCancel = new Subject();
readonly nzOnConfirm = new Subject();
@@ -227,6 +231,8 @@ export class NzPopconfirmComponent extends NzToolTipComponent implements OnDestr
override _prefix = 'ant-popover';
+ confirmLoading = false;
+
constructor(
cdr: ChangeDetectorRef,
private elementRef: ElementRef,
@@ -262,14 +268,37 @@ export class NzPopconfirmComponent extends NzToolTipComponent implements OnDestr
this.restoreFocus();
}
+ handleConfirm(): void {
+ this.nzOnConfirm.next();
+ super.hide();
+ }
+
onCancel(): void {
this.nzOnCancel.next();
super.hide();
}
onConfirm(): void {
- this.nzOnConfirm.next();
- super.hide();
+ if (this.nzBeforeConfirm) {
+ const observable = wrapIntoObservable(this.nzBeforeConfirm()).pipe(first());
+ this.confirmLoading = true;
+ observable
+ .pipe(
+ finalize(() => {
+ this.confirmLoading = false;
+ this.cdr.markForCheck();
+ }),
+ takeUntil(this.nzVisibleChange),
+ takeUntil(this.destroy$)
+ )
+ .subscribe(value => {
+ if (value) {
+ this.handleConfirm();
+ }
+ });
+ } else {
+ this.handleConfirm();
+ }
}
private capturePreviouslyFocusedElement(): void {