From f2e45ea3aff512f468649a4227ebbe5b7ce83cd0 Mon Sep 17 00:00:00 2001 From: Wendell Date: Tue, 26 Feb 2019 21:22:56 +0800 Subject: [PATCH] feat(module:message,notification): add close event (#2952) * feat(module:message, notification): add close event close #2458 * docs: fix Chinese doc not translated * fix: remove redundant declaration --- components/message/demo/close.md | 15 +++++++++++ components/message/demo/close.ts | 24 +++++++++++++++++ components/message/doc/index.en-US.md | 10 +++++++ components/message/doc/index.zh-CN.md | 10 +++++++ .../message/nz-message-container.component.ts | 26 +++++++++++++++---- components/message/nz-message.component.ts | 6 ++--- components/message/nz-message.definitions.ts | 23 +++++++++++----- components/message/nz-message.service.ts | 10 +++---- components/message/nz-message.spec.ts | 14 ++++++++++ components/notification/doc/index.en-US.md | 12 ++++++++- components/notification/doc/index.zh-CN.md | 12 ++++++++- .../nz-notification-container.component.ts | 2 ++ .../notification/nz-notification.component.ts | 2 +- .../nz-notification.definitions.ts | 5 +++- .../notification/nz-notification.spec.ts | 18 +++++++++++++ 15 files changed, 165 insertions(+), 24 deletions(-) create mode 100644 components/message/demo/close.md create mode 100644 components/message/demo/close.ts diff --git a/components/message/demo/close.md b/components/message/demo/close.md new file mode 100644 index 00000000000..ac803a52419 --- /dev/null +++ b/components/message/demo/close.md @@ -0,0 +1,15 @@ +--- +order: 4 +title: + zh-CN: 结束事件 + en-US: Customize duration +--- + +## zh-CN + +可通过订阅 `onClose` 事件在 message 关闭时做出某些操作。以上用例将依次打开三个 message。 + +## en-US + +You can subscribe to `onClose` event to make some operations. This case would open three messages in sequence. + diff --git a/components/message/demo/close.ts b/components/message/demo/close.ts new file mode 100644 index 00000000000..c7d4ee02080 --- /dev/null +++ b/components/message/demo/close.ts @@ -0,0 +1,24 @@ +import { Component } from '@angular/core'; +import { NzMessageService } from 'ng-zorro-antd'; +import { concatMap } from 'rxjs/operators'; + +@Component({ + selector: 'nz-demo-message-close', + template: ` + + `, + styles : [] +}) +export class NzDemoMessageCloseComponent { + constructor(private message: NzMessageService) { + } + + startShowMessages(): void { + this.message.loading('Action in progress', { nzDuration: 2500 }).onClose.pipe( + concatMap(() => this.message.success('Loading finished', { nzDuration: 2500 }).onClose), + concatMap(() => this.message.info('Loading finished is finished', { nzDuration: 2500 }).onClose) + ).subscribe(() => { + console.log('All completed!'); + }); + } +} diff --git a/components/message/doc/index.en-US.md b/components/message/doc/index.en-US.md index bf553739746..2a6da581a96 100644 --- a/components/message/doc/index.en-US.md +++ b/components/message/doc/index.en-US.md @@ -64,3 +64,13 @@ Methods for destruction are also provided: | nzMaxStack | The maximum number of messages that can be displayed at the same time | `number` | `8` | | nzPauseOnHover | Do not remove automatically when mouse is over while setting to `true` | `boolean` | `true` | | nzAnimate | Whether to turn on animation | `boolean` | `true` | + +### NzMessageDataFilled + +It's the object that returned when you call `NzMessageService.success` and others. + +```ts +export interface NzMessageDataFilled { + onClose: Subject; // It would emit an event when the message is closed +} +``` diff --git a/components/message/doc/index.zh-CN.md b/components/message/doc/index.zh-CN.md index 17ed2b1ee98..5818f1e2a80 100644 --- a/components/message/doc/index.zh-CN.md +++ b/components/message/doc/index.zh-CN.md @@ -65,3 +65,13 @@ title: Message | nzMaxStack | 同一时间可展示的最大提示数量 | `number` | `8` | | nzPauseOnHover | 鼠标移上时禁止自动移除 | `boolean` | `true` | | nzAnimate | 开关动画效果 | `boolean` | `true` | + +### NzMessageDataFilled + +当你调用 `NzMessageService.success` 或其他方法时会返回该对象。 + +```ts +export interface NzMessageDataFilled { + onClose: Subject; // 当 message 关闭时它会派发一个事件 +} +``` diff --git a/components/message/nz-message-container.component.ts b/components/message/nz-message-container.component.ts index 80ae97ce240..3aae42846d1 100644 --- a/components/message/nz-message-container.component.ts +++ b/components/message/nz-message-container.component.ts @@ -1,4 +1,5 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Optional, ViewEncapsulation } from '@angular/core'; +import { Subject } from 'rxjs'; import { NzMessageConfig, NZ_MESSAGE_CONFIG, NZ_MESSAGE_DEFAULT_CONFIG } from './nz-message-config'; import { NzMessageDataFilled, NzMessageDataOptions } from './nz-message.definitions'; @@ -26,34 +27,49 @@ export class NzMessageContainerComponent { this.config = { ...this.config, ...config }; } - // Create a new message + /** + * Create a new message. + * @param message Parsed message configuration. + */ createMessage(message: NzMessageDataFilled): void { if (this.messages.length >= this.config.nzMaxStack) { this.messages.splice(0, 1); } message.options = this._mergeMessageOptions(message.options); + message.onClose = new Subject(); this.messages.push(message); this.cdr.detectChanges(); } - // Remove a message by messageId - removeMessage(messageId: string): void { + /** + * Remove a message by `messageId`. + * @param messageId Id of the message to be removed. + * @param userAction Whether this is closed by user interaction. + */ + removeMessage(messageId: string, userAction: boolean = false): void { this.messages.some((message, index) => { if (message.messageId === messageId) { this.messages.splice(index, 1); this.cdr.detectChanges(); + message.onClose.next(userAction); + message.onClose.complete(); return true; } }); } - // Remove all messages + /** + * Remove all messages. + */ removeMessageAll(): void { this.messages = []; this.cdr.detectChanges(); } - // Merge default options and custom message options + /** + * Merge default options and custom message options + * @param options + */ protected _mergeMessageOptions(options: NzMessageDataOptions): NzMessageDataOptions { const defaultOptions: NzMessageDataOptions = { nzDuration : this.config.nzDuration, diff --git a/components/message/nz-message.component.ts b/components/message/nz-message.component.ts index 2d521dcb8cd..0016a36deb4 100644 --- a/components/message/nz-message.component.ts +++ b/components/message/nz-message.component.ts @@ -73,13 +73,13 @@ export class NzMessageComponent implements OnInit, OnDestroy { } // Remove self - protected _destroy(): void { + protected _destroy(userAction: boolean = false): void { if (this._options.nzAnimate) { this.nzMessage.state = 'leave'; this.cdr.detectChanges(); - setTimeout(() => this._messageContainer.removeMessage(this.nzMessage.messageId), 200); + setTimeout(() => this._messageContainer.removeMessage(this.nzMessage.messageId, userAction), 200); } else { - this._messageContainer.removeMessage(this.nzMessage.messageId); + this._messageContainer.removeMessage(this.nzMessage.messageId, userAction); } } diff --git a/components/message/nz-message.definitions.ts b/components/message/nz-message.definitions.ts index 1d72ede6348..440baaa1643 100644 --- a/components/message/nz-message.definitions.ts +++ b/components/message/nz-message.definitions.ts @@ -1,20 +1,29 @@ +import { Subject } from 'rxjs'; + +export type NzMessageType = 'success' | 'info' | 'warning' | 'error' | 'loading'; + export interface NzMessageDataOptions { nzDuration?: number; nzAnimate?: boolean; nzPauseOnHover?: boolean; } -// Message data for terminal users +/** + * Message data for terminal users. + */ export interface NzMessageData { - // TODO: remove the literal parts as it's widened anyway - type?: 'success' | 'info' | 'warning' | 'error' | 'loading' | string; + type?: NzMessageType | string; content?: string; } -// Filled version of NzMessageData (includes more private properties) +/** + * Filled version of NzMessageData (includes more private properties). + */ export interface NzMessageDataFilled extends NzMessageData { - messageId: string; // Service-wide unique id, auto generated - state?: 'enter' | 'leave'; + messageId: string; + createdAt: Date; + options?: NzMessageDataOptions; - createdAt: Date; // Auto created + state?: 'enter' | 'leave'; + onClose?: Subject; } diff --git a/components/message/nz-message.service.ts b/components/message/nz-message.service.ts index b3ecd9acde3..b13fbe8bcaa 100644 --- a/components/message/nz-message.service.ts +++ b/components/message/nz-message.service.ts @@ -5,7 +5,7 @@ import { NzMessageConfig } from './nz-message-config'; import { NzMessageContainerComponent } from './nz-message-container.component'; import { NzMessageData, NzMessageDataFilled, NzMessageDataOptions } from './nz-message.definitions'; -let globalCounter = 0; // global ID counter for messages +let globalCounter = 0; export class NzMessageBaseService { protected _container: ContainerClass; @@ -16,9 +16,8 @@ export class NzMessageBaseService { tick(1000); expect(overlayContainerElement.textContent).toContain('EXISTS'); })); + + it('should emit event when message close', fakeAsync(() => { + let onCloseFlag = false; + + const msg = messageService.create('loading', 'CLOSE'); + msg.onClose.subscribe(() => { + onCloseFlag = true; + }); + + demoAppFixture.detectChanges(); + tick(50000); + + expect(onCloseFlag).toBeTruthy(); + })); }); @Component({ diff --git a/components/notification/doc/index.en-US.md b/components/notification/doc/index.en-US.md index dad9ced2bf3..9638ee1f4b4 100644 --- a/components/notification/doc/index.en-US.md +++ b/components/notification/doc/index.en-US.md @@ -79,4 +79,14 @@ Methods for destruction are also provided: | nzAnimate | Whether to turn on animation | `boolean` | `true` | | nzTop | The top of the notification when it pops up from the top. | `string` | 24px | | nzBottom | The bottom of the notification when it pops up from the bottom. | `string` | 24px | -| nzPlacement | Popup position, optional `topLeft` `topRight` `bottomLeft` `bottomRight` | `string` | `topRight` | \ No newline at end of file +| nzPlacement | Popup position, optional `topLeft` `topRight` `bottomLeft` `bottomRight` | `string` | `topRight` | + +### NzNotificationDataFilled + +It's the object that returned when you call `NzNotificationService.success` and others. + +```ts +export interface NzNotificationDataFilled { + onClose: Subject; // It would emit an event when the notification is closed, and emit a `true` if it's closed by user +} +``` diff --git a/components/notification/doc/index.zh-CN.md b/components/notification/doc/index.zh-CN.md index 2e39e4f4b47..a56fbc3d2e0 100644 --- a/components/notification/doc/index.zh-CN.md +++ b/components/notification/doc/index.zh-CN.md @@ -79,4 +79,14 @@ subtitle: 通知提醒框 | nzAnimate | 开关动画效果 | `boolean` | `true` | | nzTop | 消息从顶部弹出时,距离顶部的位置。 | `string` | 24px | | nzBottom | 消息从底部弹出时,距离底部的位置。 | `string` | 24px | -| nzPlacement | 弹出位置,可选 `topLeft` `topRight` `bottomLeft` `bottomRight` | `string` | `topRight` | \ No newline at end of file +| nzPlacement | 弹出位置,可选 `topLeft` `topRight` `bottomLeft` `bottomRight` | `string` | `topRight` | + +### NzNotificationDataFilled + +当你调用 `NzNotificationService.success` 或其他方法时会返回该对象。 + +```ts +export interface NzNotificationDataFilled { + onClose: Subject; // 当 notification 关闭时它会派发一个事件,如果为用户手动关闭会派发 `true` +} +``` diff --git a/components/notification/nz-notification-container.component.ts b/components/notification/nz-notification-container.component.ts index 8c34928e044..19e1a8a6003 100644 --- a/components/notification/nz-notification-container.component.ts +++ b/components/notification/nz-notification-container.component.ts @@ -1,4 +1,5 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Optional, ViewEncapsulation } from '@angular/core'; +import { Subject } from 'rxjs'; import { NzMessageContainerComponent } from '../message/nz-message-container.component'; import { NzNotificationConfig, NZ_NOTIFICATION_CONFIG, NZ_NOTIFICATION_DEFAULT_CONFIG } from './nz-notification-config'; @@ -34,6 +35,7 @@ export class NzNotificationContainerComponent extends NzMessageContainerComponen */ createMessage(notification: NzNotificationDataFilled): void { notification.options = this._mergeMessageOptions(notification.options); + notification.onClose = new Subject(); const key = notification.options.nzKey; const notificationWithSameKey = this.messages.find(msg => msg.options.nzKey === notification.options.nzKey); if (key && notificationWithSameKey) { diff --git a/components/notification/nz-notification.component.ts b/components/notification/nz-notification.component.ts index c088c805149..0d44c89395f 100644 --- a/components/notification/nz-notification.component.ts +++ b/components/notification/nz-notification.component.ts @@ -19,7 +19,7 @@ export class NzNotificationComponent extends NzMessageComponent { } close(): void { - this._destroy(); + this._destroy(true); } get state(): string { diff --git a/components/notification/nz-notification.definitions.ts b/components/notification/nz-notification.definitions.ts index e45effc5d2c..89a5a949d08 100644 --- a/components/notification/nz-notification.definitions.ts +++ b/components/notification/nz-notification.definitions.ts @@ -1,4 +1,5 @@ import { TemplateRef } from '@angular/core'; +import { Subject } from 'rxjs'; import { NzMessageData, NzMessageDataOptions } from '../message/nz-message.definitions'; @@ -21,7 +22,9 @@ export interface NzNotificationDataOptions extends NzMessageDataOptions // Filled version of NzMessageData (includes more private properties) export interface NzNotificationDataFilled extends NzNotificationData { messageId: string; // Service-wide unique id, auto generated + createdAt: Date; // Auto created + state?: 'enter' | 'leave'; options?: NzNotificationDataOptions; - createdAt: Date; // Auto created + onClose?: Subject; } diff --git a/components/notification/nz-notification.spec.ts b/components/notification/nz-notification.spec.ts index 9119c0163b5..9e567dd6c91 100644 --- a/components/notification/nz-notification.spec.ts +++ b/components/notification/nz-notification.spec.ts @@ -175,6 +175,24 @@ describe('NzNotification', () => { expect(overlayContainerElement.textContent).toContain('SHOULD NOT CHANGE'); expect(overlayContainerElement.querySelector('.ant-notification-notice-icon-success')).not.toBeNull(); }); + + it('should receive `true` when it is closed by user', fakeAsync(() => { + let onCloseFlag = false; + + messageService.create(null, null, 'close').onClose.subscribe(user => { + if (user) { + onCloseFlag = true; + } + }); + + demoAppFixture.detectChanges(); + tick(1000); + const closeEl = overlayContainerElement.querySelector('.ant-notification-notice-close'); + dispatchMouseEvent(closeEl, 'click'); + tick(1000); + expect(onCloseFlag).toBeTruthy(); + tick(50000); + })); }); @Component({