Skip to content

Commit 71ead99

Browse files
authored
fix(module:message): clean up DOM after usage (#7965)
Call `overlayRef.dispose()` when all instances of messages have disappeared Resolves #7772
1 parent 7470ed6 commit 71ead99

File tree

4 files changed

+94
-34
lines changed

4 files changed

+94
-34
lines changed

components/core/services/singleton.ts

+6
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ export class NzSingletonService {
4747
}
4848
}
4949

50+
unregisterSingletonWithKey(key: string): void {
51+
if (this.singletonRegistry.has(key)) {
52+
this.singletonRegistry.delete(key);
53+
}
54+
}
55+
5056
getSingletonWithKey<T>(key: string): T | null {
5157
return this.singletonRegistry.has(key) ? (this.singletonRegistry.get(key)!.target as T) : null;
5258
}

components/message/base.ts

+19
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ export abstract class NzMNService {
6060
if (!containerInstance) {
6161
this.container = containerInstance = componentRef.instance;
6262
this.nzSingletonService.registerSingletonWithKey(this.componentPrefix, containerInstance);
63+
this.container.afterAllInstancesRemoved.subscribe(() => {
64+
this.container = undefined;
65+
this.nzSingletonService.unregisterSingletonWithKey(this.componentPrefix);
66+
overlayRef.dispose();
67+
});
6368
}
6469

6570
return containerInstance as T;
@@ -71,6 +76,10 @@ export abstract class NzMNContainerComponent implements OnInit, OnDestroy {
7176
config?: Required<MessageConfig>;
7277
instances: Array<Required<NzMessageData>> = [];
7378

79+
private readonly _afterAllInstancesRemoved = new Subject<void>();
80+
81+
readonly afterAllInstancesRemoved = this._afterAllInstancesRemoved.asObservable();
82+
7483
protected readonly destroy$ = new Subject<void>();
7584

7685
constructor(protected cdr: ChangeDetectorRef, protected nzConfigService: NzConfigService) {
@@ -110,13 +119,18 @@ export abstract class NzMNContainerComponent implements OnInit, OnDestroy {
110119
this.onRemove(instance, userAction);
111120
this.readyInstances();
112121
});
122+
123+
if (!this.instances.length) {
124+
this.onAllInstancesRemoved();
125+
}
113126
}
114127

115128
removeAll(): void {
116129
this.instances.forEach(i => this.onRemove(i, false));
117130
this.instances = [];
118131

119132
this.readyInstances();
133+
this.onAllInstancesRemoved();
120134
}
121135

122136
protected onCreate(instance: NzMessageData): Required<NzMessageData> {
@@ -130,6 +144,11 @@ export abstract class NzMNContainerComponent implements OnInit, OnDestroy {
130144
instance.onClose.complete();
131145
}
132146

147+
private onAllInstancesRemoved(): void {
148+
this._afterAllInstancesRemoved.next();
149+
this._afterAllInstancesRemoved.complete();
150+
}
151+
133152
protected readyInstances(): void {
134153
this.cdr.detectChanges();
135154
}

components/message/message.spec.ts

+25-14
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ import { NzMessageService } from './message.service';
1313
describe('message', () => {
1414
let testBed: ComponentBed<NzTestMessageComponent>;
1515
let messageService: NzMessageService;
16+
let overlayContainer: OverlayContainer;
1617
let overlayContainerElement: HTMLElement;
1718
let fixture: ComponentFixture<NzTestMessageComponent>;
1819
let testComponent: NzTestMessageComponent;
19-
let nzConfigService: NzConfigService;
20+
let configService: NzConfigService;
2021

2122
beforeEach(fakeAsync(() => {
2223
testBed = createComponentBed(NzTestMessageComponent, {
@@ -38,17 +39,14 @@ describe('message', () => {
3839
testComponent = testBed.component;
3940
}));
4041

41-
beforeEach(inject([NzMessageService, OverlayContainer], (m: NzMessageService, oc: OverlayContainer) => {
42-
messageService = m;
43-
// need init before testing
44-
const message = messageService.success('init');
45-
messageService.remove(message.messageId);
46-
// @ts-ignore
47-
nzConfigService = messageService.container.nzConfigService;
48-
if (!overlayContainerElement) {
49-
overlayContainerElement = oc.getContainerElement();
42+
beforeEach(inject(
43+
[NzMessageService, OverlayContainer, NzConfigService],
44+
(m: NzMessageService, oc: OverlayContainer, c: NzConfigService) => {
45+
messageService = m;
46+
overlayContainer = oc;
47+
configService = c;
5048
}
51-
}));
49+
));
5250

5351
afterEach(() => {
5452
messageService.remove();
@@ -57,6 +55,7 @@ describe('message', () => {
5755
it('should open a message box with success', () => {
5856
messageService.success('SUCCESS');
5957
fixture.detectChanges();
58+
overlayContainerElement = overlayContainer.getContainerElement();
6059

6160
expect((overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement).style.zIndex).toBe('1010');
6261
expect(overlayContainerElement.textContent).toContain('SUCCESS');
@@ -66,13 +65,15 @@ describe('message', () => {
6665
it('should open a message box with error', () => {
6766
messageService.error('ERROR');
6867
fixture.detectChanges();
68+
overlayContainerElement = overlayContainer.getContainerElement();
6969
expect(overlayContainerElement.textContent).toContain('ERROR');
7070
expect(overlayContainerElement.querySelector('.anticon-close-circle')).not.toBeNull();
7171
});
7272

7373
it('should open a message box with warning', () => {
7474
messageService.warning('WARNING');
7575
fixture.detectChanges();
76+
overlayContainerElement = overlayContainer.getContainerElement();
7677

7778
expect(overlayContainerElement.textContent).toContain('WARNING');
7879
expect(overlayContainerElement.querySelector('.anticon-exclamation-circle')).not.toBeNull();
@@ -81,6 +82,7 @@ describe('message', () => {
8182
it('should open a message box with info', () => {
8283
messageService.info('INFO');
8384
fixture.detectChanges();
85+
overlayContainerElement = overlayContainer.getContainerElement();
8486

8587
expect(overlayContainerElement.textContent).toContain('INFO');
8688
expect(overlayContainerElement.querySelector('.anticon-info-circle')).not.toBeNull();
@@ -89,6 +91,7 @@ describe('message', () => {
8991
it('should open a message box with loading', () => {
9092
messageService.loading('LOADING');
9193
fixture.detectChanges();
94+
overlayContainerElement = overlayContainer.getContainerElement();
9295

9396
expect(overlayContainerElement.textContent).toContain('LOADING');
9497
expect(overlayContainerElement.querySelector('.anticon-loading')).not.toBeNull();
@@ -97,6 +100,7 @@ describe('message', () => {
97100
it('should support template', fakeAsync(() => {
98101
messageService.info(testComponent.template);
99102
fixture.detectChanges();
103+
overlayContainerElement = overlayContainer.getContainerElement();
100104

101105
expect(overlayContainerElement.textContent).toContain('Content in template');
102106
tick(10000);
@@ -105,6 +109,7 @@ describe('message', () => {
105109
it('should auto closed by 1s', fakeAsync(() => {
106110
messageService.create('', 'EXISTS', { nzDuration: 1000 });
107111
fixture.detectChanges();
112+
overlayContainerElement = overlayContainer.getContainerElement();
108113

109114
expect(overlayContainerElement.textContent).toContain('EXISTS');
110115

@@ -115,6 +120,7 @@ describe('message', () => {
115120
it('should not destroy when hovered', fakeAsync(() => {
116121
messageService.create('', 'EXISTS', { nzDuration: 3000 });
117122
fixture.detectChanges();
123+
overlayContainerElement = overlayContainer.getContainerElement();
118124

119125
const messageElement = overlayContainerElement.querySelector('.ant-message-notice')!;
120126
dispatchMouseEvent(messageElement, 'mouseenter');
@@ -129,6 +135,7 @@ describe('message', () => {
129135
it('should not destroyed automatically but manually', fakeAsync(() => {
130136
const filledMessage = messageService.success('SUCCESS', { nzDuration: 0 });
131137
fixture.detectChanges();
138+
overlayContainerElement = overlayContainer.getContainerElement();
132139

133140
tick(50000);
134141
expect(overlayContainerElement.textContent).toContain('SUCCESS');
@@ -145,6 +152,7 @@ describe('message', () => {
145152
fixture.detectChanges();
146153
tick();
147154
fixture.detectChanges();
155+
overlayContainerElement = overlayContainer.getContainerElement();
148156

149157
expect(overlayContainerElement.textContent).toContain(content);
150158
if (id === 3) {
@@ -156,7 +164,7 @@ describe('message', () => {
156164
messageService.remove();
157165
fixture.detectChanges();
158166
expect(overlayContainerElement.textContent).not.toContain('SUCCESS-3');
159-
expect((messageService as any).container.instances.length).toBe(0); // eslint-disable-line @typescript-eslint/no-explicit-any
167+
expect((messageService as any).container).toBeUndefined(); // eslint-disable-line @typescript-eslint/no-explicit-any
160168
}));
161169

162170
it('should destroy without animation', fakeAsync(() => {
@@ -167,10 +175,11 @@ describe('message', () => {
167175
}));
168176

169177
it('should reset default config from config service', fakeAsync(() => {
170-
nzConfigService.set('message', { nzDuration: 0 });
178+
configService.set('message', { nzDuration: 0 });
171179
messageService.create('loading', 'EXISTS');
172180
fixture.detectChanges();
173181
tick(10000);
182+
overlayContainerElement = overlayContainer.getContainerElement();
174183
expect(overlayContainerElement.textContent).toContain('EXISTS');
175184
}));
176185

@@ -190,6 +199,7 @@ describe('message', () => {
190199
messageService.create('top', 'CHANGE');
191200
fixture.detectChanges();
192201

202+
overlayContainerElement = overlayContainer.getContainerElement();
193203
const messageContainerElement = overlayContainerElement.querySelector('.ant-message') as HTMLElement;
194204
expect(messageContainerElement.style.top).toBe('24px');
195205

@@ -198,9 +208,10 @@ describe('message', () => {
198208

199209
describe('RTL', () => {
200210
it('should apply classname', () => {
201-
nzConfigService.set('message', { nzDirection: 'rtl' });
211+
configService.set('message', { nzDirection: 'rtl' });
202212
messageService.info('INFO');
203213
fixture.detectChanges();
214+
overlayContainerElement = overlayContainer.getContainerElement();
204215
expect(overlayContainerElement.textContent).toContain('INFO');
205216
expect(overlayContainerElement.querySelector('.ant-message-rtl')).not.toBeNull();
206217
});

0 commit comments

Comments
 (0)