Skip to content

Commit 797b261

Browse files
rorry121luolei
and
luolei
authored
feat(module: popconfirm): support async close (#7533)
Co-authored-by: luolei <luolei@kuaishou.com>
1 parent 60beabc commit 797b261

File tree

8 files changed

+219
-5
lines changed

8 files changed

+219
-5
lines changed

Diff for: components/popconfirm/demo/async.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
order: 6
3+
title:
4+
zh-CN: 异步关闭
5+
en-US: Asynchronously close
6+
---
7+
8+
## zh-CN
9+
10+
点击确定后异步关闭 Popconfirm,例如提交表单。
11+
12+
## en-US
13+
14+
Asynchronously close a popconfirm when a the OK button is pressed. For example, you can use this pattern when you submit a form.
15+
16+

Diff for: components/popconfirm/demo/async.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Component } from '@angular/core';
2+
import { Observable } from 'rxjs';
3+
4+
import { NzMessageService } from 'ng-zorro-antd/message';
5+
6+
@Component({
7+
selector: 'nz-demo-popconfirm-async',
8+
template: `
9+
<button
10+
nz-popconfirm
11+
nzPopconfirmTitle="Title"
12+
[nzBeforeConfirm]="beforeConfirm"
13+
(nzOnConfirm)="confirm()"
14+
(nzOnCancel)="cancel()"
15+
nz-button
16+
nzType="primary"
17+
>
18+
Open Popconfirm with async logic
19+
</button>
20+
`
21+
})
22+
export class NzDemoPopconfirmAsyncComponent {
23+
cancel(): void {
24+
this.nzMessageService.info('click cancel');
25+
}
26+
27+
confirm(): void {
28+
this.nzMessageService.info('click confirm');
29+
}
30+
31+
beforeConfirm(): Observable<boolean> {
32+
return new Observable(observer => {
33+
setTimeout(() => {
34+
observer.next(true);
35+
observer.complete();
36+
}, 3000);
37+
});
38+
}
39+
40+
constructor(private nzMessageService: NzMessageService) {}
41+
}

Diff for: components/popconfirm/demo/promise.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
order: 7
3+
title:
4+
zh-CN: 基于 Promise 的异步关闭
5+
en-US: Asynchronously close on Promise
6+
---
7+
8+
## zh-CN
9+
10+
点击确定后异步关闭 Popconfirm,例如提交表单。
11+
12+
## en-US
13+
14+
Asynchronously close a popconfirm when the OK button is pressed. For example, you can use this pattern when you submit a form.
15+
16+

Diff for: components/popconfirm/demo/promise.ts

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Component } from '@angular/core';
2+
3+
import { NzMessageService } from 'ng-zorro-antd/message';
4+
5+
@Component({
6+
selector: 'nz-demo-popconfirm-promise',
7+
template: `
8+
<button
9+
nz-popconfirm
10+
nzPopconfirmTitle="Title"
11+
[nzBeforeConfirm]="beforeConfirm"
12+
(nzOnConfirm)="confirm()"
13+
(nzOnCancel)="cancel()"
14+
nz-button
15+
nzType="primary"
16+
>
17+
Open Popconfirm with Promise
18+
</button>
19+
`
20+
})
21+
export class NzDemoPopconfirmPromiseComponent {
22+
cancel(): void {
23+
this.nzMessageService.info('click cancel');
24+
}
25+
26+
confirm(): void {
27+
this.nzMessageService.info('click confirm');
28+
}
29+
30+
beforeConfirm(): Promise<boolean> {
31+
return new Promise(resolve => {
32+
setTimeout(() => {
33+
resolve(true);
34+
}, 3000);
35+
});
36+
}
37+
38+
constructor(private nzMessageService: NzMessageService) {}
39+
}

Diff for: components/popconfirm/doc/index.en-US.md

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';
4646
| `[nzCondition]` | Whether to directly emit `onConfirm` without showing Popconfirm | `boolean` | `false` | - |
4747
| `[nzIcon]` | Customize icon of confirmation | `string \| TemplateRef<void>` | - | - |
4848
| `[nzAutoFocus]` | Autofocus a button | `null \| 'ok' \| 'cancel'` | `null` ||
49+
| `[nzBeforeConfirm]` | The hook before the confirmation operation, decides whether to continue responding to the `nzOnConfirm` callback, supports asynchronous verification. | `(() => Observable<boolean> \| Promise<boolean> \| boolean) \| null` | `null` | - |
4950
| `(nzOnCancel)` | Callback of cancel | `EventEmitter<void>` | - | - |
5051
| `(nzOnConfirm)` | Callback of confirmation | `EventEmitter<void>` | - | - |
5152

Diff for: components/popconfirm/doc/index.zh-CN.md

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';
5050
| `[nzCondition]` | 是否直接触发 `nzOnConfirm` 而不弹出框 | `boolean` | `false` | - |
5151
| `[nzIcon]` | 自定义弹出框的 icon | `string \| TemplateRef<void>` | - | - |
5252
| `[nzAutoFocus]` | 按钮的自动聚焦 | `null \| 'ok' \| 'cancel'` | `null` ||
53+
| `[nzBeforeConfirm]` | 确认操作之前的钩子,决定是否继续响应 `nzOnConfirm` 回调,支持异步验证。 | `(() => Observable<boolean> \| Promise<boolean> \| boolean) \| null` | `null` | - |
5354
| `(nzOnCancel)` | 点击取消的回调 | `EventEmitter<void>` | - | - |
5455
| `(nzOnConfirm)` | 点击确认的回调 | `EventEmitter<void>` | - | - |
5556

Diff for: components/popconfirm/popconfirm.spec.ts

+71
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Component, ElementRef, ViewChild } from '@angular/core';
33
import { ComponentFixture, fakeAsync, inject, tick } from '@angular/core/testing';
44
import { By } from '@angular/platform-browser';
55
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
6+
import { Observable } from 'rxjs';
67

78
import { NzButtonType } from 'ng-zorro-antd/button';
89
import { dispatchMouseEvent } from 'ng-zorro-antd/core/testing';
@@ -144,6 +145,74 @@ describe('NzPopconfirm', () => {
144145
expect(component.cancel).toHaveBeenCalledTimes(0);
145146
}));
146147

148+
it('should before confirm work', fakeAsync(() => {
149+
const triggerElement = component.stringTemplate.nativeElement;
150+
151+
dispatchMouseEvent(triggerElement, 'click');
152+
fixture.detectChanges();
153+
expect(getTitleText()!.textContent).toContain('title-string');
154+
expect(component.confirm).toHaveBeenCalledTimes(0);
155+
expect(component.cancel).toHaveBeenCalledTimes(0);
156+
157+
component.beforeConfirm = () => false;
158+
fixture.detectChanges();
159+
160+
dispatchMouseEvent(getTooltipTrigger(1), 'click');
161+
waitingForTooltipToggling();
162+
expect(component.confirm).toHaveBeenCalledTimes(0);
163+
expect(component.cancel).toHaveBeenCalledTimes(0);
164+
expect(getTitleText()!.textContent).toContain('title-string');
165+
}));
166+
167+
it('should before confirm observable work', fakeAsync(() => {
168+
const triggerElement = component.stringTemplate.nativeElement;
169+
170+
dispatchMouseEvent(triggerElement, 'click');
171+
fixture.detectChanges();
172+
expect(getTitleText()!.textContent).toContain('title-string');
173+
expect(component.confirm).toHaveBeenCalledTimes(0);
174+
expect(component.cancel).toHaveBeenCalledTimes(0);
175+
176+
component.beforeConfirm = () =>
177+
new Observable(observer => {
178+
setTimeout(() => {
179+
observer.next(true);
180+
observer.complete();
181+
}, 200);
182+
});
183+
184+
dispatchMouseEvent(getTooltipTrigger(1), 'click');
185+
tick(200 + 10);
186+
waitingForTooltipToggling();
187+
expect(getTitleText()).toBeNull();
188+
expect(component.confirm).toHaveBeenCalledTimes(1);
189+
expect(component.cancel).toHaveBeenCalledTimes(0);
190+
}));
191+
192+
it('should before confirm promise work', fakeAsync(() => {
193+
const triggerElement = component.stringTemplate.nativeElement;
194+
195+
dispatchMouseEvent(triggerElement, 'click');
196+
fixture.detectChanges();
197+
expect(getTitleText()!.textContent).toContain('title-string');
198+
expect(component.confirm).toHaveBeenCalledTimes(0);
199+
expect(component.cancel).toHaveBeenCalledTimes(0);
200+
201+
component.beforeConfirm = () =>
202+
new Promise(resolve => {
203+
setTimeout(() => {
204+
resolve(true);
205+
}, 200);
206+
});
207+
208+
dispatchMouseEvent(getTooltipTrigger(1), 'click');
209+
tick(200 + 10);
210+
waitingForTooltipToggling();
211+
expect(getTitleText()).toBeNull();
212+
expect(component.confirm).toHaveBeenCalledTimes(1);
213+
expect(component.cancel).toHaveBeenCalledTimes(0);
214+
}));
215+
147216
it('should nzPopconfirmShowArrow work', fakeAsync(() => {
148217
const triggerElement = component.stringTemplate.nativeElement;
149218
dispatchMouseEvent(triggerElement, 'click');
@@ -178,6 +247,7 @@ describe('NzPopconfirm', () => {
178247
nzCancelText="cancel-text"
179248
[nzAutofocus]="autoFocus"
180249
[nzCondition]="condition"
250+
[nzBeforeConfirm]="beforeConfirm"
181251
[nzPopconfirmShowArrow]="nzPopconfirmShowArrow"
182252
[nzPopconfirmBackdrop]="nzPopconfirmBackdrop"
183253
(nzOnConfirm)="confirm()"
@@ -210,6 +280,7 @@ export class NzPopconfirmTestNewComponent {
210280
icon: string | undefined = undefined;
211281
nzPopconfirmBackdrop = false;
212282
autoFocus: NzAutoFocusType = null;
283+
beforeConfirm: (() => Observable<boolean> | Promise<boolean> | boolean) | null = null;
213284

214285
@ViewChild('stringTemplate', { static: false }) stringTemplate!: ElementRef;
215286
@ViewChild('templateTemplate', { static: false }) templateTemplate!: ElementRef;

Diff for: components/popconfirm/popconfirm.ts

+34-5
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ import {
2727
ViewContainerRef,
2828
ViewEncapsulation
2929
} from '@angular/core';
30-
import { Subject } from 'rxjs';
31-
import { takeUntil } from 'rxjs/operators';
30+
import { Observable, Subject } from 'rxjs';
31+
import { finalize, first, takeUntil } from 'rxjs/operators';
3232

3333
import { NzButtonType } from 'ng-zorro-antd/button';
3434
import { zoomBigMotion } from 'ng-zorro-antd/core/animation';
3535
import { NzConfigKey, NzConfigService, WithConfig } from 'ng-zorro-antd/core/config';
3636
import { NzNoAnimationDirective } from 'ng-zorro-antd/core/no-animation';
3737
import { BooleanInput, NgStyleInterface, NzSafeAny, NzTSType } from 'ng-zorro-antd/core/types';
38-
import { InputBoolean } from 'ng-zorro-antd/core/util';
38+
import { InputBoolean, wrapIntoObservable } from 'ng-zorro-antd/core/util';
3939
import { NzTooltipBaseDirective, NzToolTipComponent, NzTooltipTrigger, PropertyMapping } from 'ng-zorro-antd/tooltip';
4040

4141
export type NzAutoFocusType = null | 'ok' | 'cancel';
@@ -70,6 +70,7 @@ export class NzPopconfirmDirective extends NzTooltipBaseDirective {
7070
@Input() nzOkType?: string;
7171
@Input() nzOkDanger?: boolean;
7272
@Input() nzCancelText?: string;
73+
@Input() nzBeforeConfirm?: () => Observable<boolean> | Promise<boolean> | boolean;
7374
@Input() nzIcon?: string | TemplateRef<void>;
7475
@Input() @InputBoolean() nzCondition: boolean = false;
7576
@Input() @InputBoolean() nzPopconfirmShowArrow: boolean = true;
@@ -90,6 +91,7 @@ export class NzPopconfirmDirective extends NzTooltipBaseDirective {
9091
nzOkType: ['nzOkType', () => this.nzOkType],
9192
nzOkDanger: ['nzOkDanger', () => this.nzOkDanger],
9293
nzCancelText: ['nzCancelText', () => this.nzCancelText],
94+
nzBeforeConfirm: ['nzBeforeConfirm', () => this.nzBeforeConfirm],
9395
nzCondition: ['nzCondition', () => this.nzCondition],
9496
nzIcon: ['nzIcon', () => this.nzIcon],
9597
nzPopconfirmShowArrow: ['nzPopconfirmShowArrow', () => this.nzPopconfirmShowArrow],
@@ -190,6 +192,7 @@ export class NzPopconfirmDirective extends NzTooltipBaseDirective {
190192
[nzSize]="'small'"
191193
[nzType]="nzOkType !== 'danger' ? nzOkType : 'primary'"
192194
[nzDanger]="nzOkDanger || nzOkType === 'danger'"
195+
[nzLoading]="confirmLoading"
193196
(click)="onConfirm()"
194197
[attr.cdkFocusInitial]="nzAutoFocus === 'ok' || null"
195198
>
@@ -217,6 +220,7 @@ export class NzPopconfirmComponent extends NzToolTipComponent implements OnDestr
217220
nzOkType: NzButtonType | 'danger' = 'primary';
218221
nzOkDanger: boolean = false;
219222
nzAutoFocus: NzAutoFocusType = null;
223+
nzBeforeConfirm: (() => Observable<boolean> | Promise<boolean> | boolean) | null = null;
220224

221225
readonly nzOnCancel = new Subject<void>();
222226
readonly nzOnConfirm = new Subject<void>();
@@ -227,6 +231,8 @@ export class NzPopconfirmComponent extends NzToolTipComponent implements OnDestr
227231

228232
override _prefix = 'ant-popover';
229233

234+
confirmLoading = false;
235+
230236
constructor(
231237
cdr: ChangeDetectorRef,
232238
private elementRef: ElementRef,
@@ -262,14 +268,37 @@ export class NzPopconfirmComponent extends NzToolTipComponent implements OnDestr
262268
this.restoreFocus();
263269
}
264270

271+
handleConfirm(): void {
272+
this.nzOnConfirm.next();
273+
super.hide();
274+
}
275+
265276
onCancel(): void {
266277
this.nzOnCancel.next();
267278
super.hide();
268279
}
269280

270281
onConfirm(): void {
271-
this.nzOnConfirm.next();
272-
super.hide();
282+
if (this.nzBeforeConfirm) {
283+
const observable = wrapIntoObservable(this.nzBeforeConfirm()).pipe(first());
284+
this.confirmLoading = true;
285+
observable
286+
.pipe(
287+
finalize(() => {
288+
this.confirmLoading = false;
289+
this.cdr.markForCheck();
290+
}),
291+
takeUntil(this.nzVisibleChange),
292+
takeUntil(this.destroy$)
293+
)
294+
.subscribe(value => {
295+
if (value) {
296+
this.handleConfirm();
297+
}
298+
});
299+
} else {
300+
this.handleConfirm();
301+
}
273302
}
274303

275304
private capturePreviouslyFocusedElement(): void {

0 commit comments

Comments
 (0)