Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.

Commit 2a92e78

Browse files
authored
fix(core): unsubscribe every possible events (#611)
* fix(core): unsubscribe every possible events #610 - causes memory heap size to keep growing * refactor: use subscriptions array instead of single subscription * fix: remove keydown binding when disposing of externalCopy plugin * fix: possible leak, body onClick event not unbound in multiple-select
1 parent 32cd122 commit 2a92e78

19 files changed

+247
-101
lines changed

src/app/examples/custom-angularComponentEditor.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ import {
88
EditorValidator,
99
EditorValidatorOutput,
1010
GridOption,
11+
unsubscribeAllObservables,
1112
} from './../modules/angular-slickgrid';
1213

1314
/*
1415
* An example of a 'detached' editor.
1516
* KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter.
1617
*/
1718
export class CustomAngularComponentEditor implements Editor {
18-
changeSubscriber: Subscription;
19+
private _subscriptions: Subscription[] = [];
1920

2021
/** Angular Component Reference */
2122
componentRef: ComponentRef<any>;
@@ -88,9 +89,9 @@ export class CustomAngularComponentEditor implements Editor {
8889
Object.assign(this.componentRef.instance, { collection: this.collection });
8990

9091
// when our model (item object) changes, we'll call a save of the slickgrid editor
91-
this.changeSubscriber = this.componentRef.instance.onItemChanged.subscribe((item) => {
92-
this.save();
93-
});
92+
this._subscriptions.push(
93+
this.componentRef.instance.onItemChanged.subscribe((item: any) => this.save())
94+
);
9495
}
9596
}
9697

@@ -131,8 +132,10 @@ export class CustomAngularComponentEditor implements Editor {
131132
destroy() {
132133
if (this.componentRef && this.componentRef.destroy) {
133134
this.componentRef.destroy();
134-
this.changeSubscriber.unsubscribe();
135135
}
136+
137+
// also unsubscribe all Angular Subscriptions
138+
this._subscriptions = unsubscribeAllObservables(this._subscriptions);
136139
}
137140

138141
/** optional, implement a focus method on your Angular Component */

src/app/examples/custom-angularComponentFilter.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ import {
1111
OperatorType,
1212
OperatorString,
1313
SearchTerm,
14+
unsubscribeAllObservables,
1415
} from './../modules/angular-slickgrid';
1516

1617
// using external non-typed js libraries
1718
declare const $: any;
1819

1920
export class CustomAngularComponentFilter implements Filter {
2021
private _shouldTriggerQuery = true;
21-
changeSubscriber: Subscription;
22+
private _subscriptions: Subscription[] = [];
2223

2324
/** Angular Component Reference */
2425
componentRef: ComponentRef<any>;
@@ -84,11 +85,13 @@ export class CustomAngularComponentFilter implements Filter {
8485
// but technically you can pass any values you wish to your Component
8586
Object.assign(componentOuput.componentRef.instance, { collection: this.collection });
8687

87-
this.changeSubscriber = componentOuput.componentRef.instance.onItemChanged.subscribe((item) => {
88-
this.callback(undefined, { columnDef: this.columnDef, operator: this.operator, searchTerms: [item.id], shouldTriggerQuery: this._shouldTriggerQuery });
89-
// reset flag for next use
90-
this._shouldTriggerQuery = true;
91-
});
88+
this._subscriptions.push(
89+
componentOuput.componentRef.instance.onItemChanged.subscribe((item) => {
90+
this.callback(undefined, { columnDef: this.columnDef, operator: this.operator, searchTerms: [item.id], shouldTriggerQuery: this._shouldTriggerQuery });
91+
// reset flag for next use
92+
this._shouldTriggerQuery = true;
93+
})
94+
);
9295
});
9396
}
9497
}
@@ -107,8 +110,10 @@ export class CustomAngularComponentFilter implements Filter {
107110
destroy() {
108111
if (this.componentRef && this.componentRef.destroy) {
109112
this.componentRef.destroy();
110-
this.changeSubscriber.unsubscribe();
111113
}
114+
115+
// also unsubscribe all Angular Subscriptions
116+
this._subscriptions = unsubscribeAllObservables(this._subscriptions);
112117
}
113118

114119
/** Set value(s) on the DOM element */

src/app/examples/grid-clientside.component.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, OnInit } from '@angular/core';
1+
import { Component, OnInit, OnDestroy } from '@angular/core';
22
import { HttpClient } from '@angular/common/http';
33
import { TranslateService } from '@ngx-translate/core';
44
import { CustomInputFilter } from './custom-inputFilter';
@@ -25,7 +25,7 @@ const URL_SAMPLE_COLLECTION_DATA = 'assets/data/collection_500_numbers.json';
2525
@Component({
2626
templateUrl: './grid-clientside.component.html'
2727
})
28-
export class GridClientSideComponent implements OnInit {
28+
export class GridClientSideComponent implements OnInit, OnDestroy {
2929
title = 'Example 4: Client Side Sort/Filter';
3030
subTitle = `
3131
Sort/Filter on client side only using SlickGrid DataView (<a href="https://github.com/ghiscoding/Angular-Slickgrid/wiki/Sorting" target="_blank">Wiki docs</a>)
@@ -280,4 +280,8 @@ export class GridClientSideComponent implements OnInit {
280280
});
281281
}
282282
}
283+
284+
ngOnDestroy() {
285+
this.angularGrid.destroy();
286+
}
283287
}

src/app/examples/grid-contextmenu.component.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
1+
import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core';
22
import { TranslateService } from '@ngx-translate/core';
3+
import { Subscription } from 'rxjs';
34
import {
45
AngularGridInstance,
56
Column,
@@ -10,6 +11,7 @@ import {
1011
Formatter,
1112
Formatters,
1213
GridOption,
14+
unsubscribeAllObservables,
1315
} from './../modules/angular-slickgrid';
1416

1517
const actionFormatter: Formatter = (row, cell, value, columnDef, dataContext) => {
@@ -58,7 +60,7 @@ const taskTranslateFormatter: Formatter = (row: number, cell: number, value: any
5860
styleUrls: ['./grid-contextmenu.component.scss'],
5961
encapsulation: ViewEncapsulation.None
6062
})
61-
export class GridContextMenuComponent implements OnInit {
63+
export class GridContextMenuComponent implements OnInit, OnDestroy {
6264
title = 'Example 26: Cell Menu & Context Menu Plugins';
6365
subTitle = `Add Cell Menu and Context Menu
6466
<ul>
@@ -86,6 +88,7 @@ export class GridContextMenuComponent implements OnInit {
8688
</ol>
8789
</ul>`;
8890

91+
private subscriptions: Subscription[] = [];
8992
angularGrid: AngularGridInstance;
9093
columnDefinitions: Column[];
9194
gridOptions: GridOption;
@@ -116,6 +119,11 @@ export class GridContextMenuComponent implements OnInit {
116119
this.dataset = this.getData(1000);
117120
}
118121

122+
ngOnDestroy() {
123+
// also unsubscribe all Angular Subscriptions
124+
this.subscriptions = unsubscribeAllObservables(this.subscriptions);
125+
}
126+
119127
prepareGrid() {
120128
this.columnDefinitions = [
121129
{ id: 'id', name: '#', field: 'id', maxWidth: 45, sortable: true, filterable: true },
@@ -453,8 +461,11 @@ export class GridContextMenuComponent implements OnInit {
453461

454462
switchLanguage() {
455463
const nextLanguage = (this.selectedLanguage === 'en') ? 'fr' : 'en';
456-
this.translate.use(nextLanguage).subscribe(() => {
457-
this.selectedLanguage = nextLanguage;
458-
});
464+
465+
this.subscriptions.push(
466+
this.translate.use(nextLanguage).subscribe(() => {
467+
this.selectedLanguage = nextLanguage;
468+
})
469+
);
459470
}
460471
}

src/app/examples/grid-frozen.component.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
1+
import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core';
22
import { AngularGridInstance, Column, ColumnEditorDualInput, Editors, FieldType, formatNumber, Formatters, Filters, GridOption } from './../modules/angular-slickgrid';
33

44
@Component({
55
templateUrl: './grid-frozen.component.html',
66
styleUrls: ['./grid-frozen.component.scss'],
77
encapsulation: ViewEncapsulation.None,
88
})
9-
export class GridFrozenComponent implements OnInit {
9+
export class GridFrozenComponent implements OnInit, OnDestroy {
1010
title = 'Example 20: Pinned (frozen) Columns/Rows';
1111
subTitle = `
1212
This example demonstrates the use of Pinned (aka frozen) Columns and/or Rows (<a href="https://github.com/ghiscoding/Angular-Slickgrid/wiki/Pinned-(aka-Frozen)-Columns-Rows" target="_blank">Wiki docs</a>)
@@ -31,6 +31,12 @@ export class GridFrozenComponent implements OnInit {
3131
this.prepareDataGrid();
3232
}
3333

34+
ngOnDestroy() {
35+
// unsubscribe every SlickGrid subscribed event (or use the Slick.EventHandler)
36+
this.gridObj.onMouseEnter.unsubscribe();
37+
this.gridObj.onMouseLeave.unsubscribe();
38+
}
39+
3440
angularGridReady(angularGrid: AngularGridInstance) {
3541
this.angularGrid = angularGrid;
3642
this.gridObj = angularGrid.slickGrid;

src/app/examples/grid-graphql.component.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
MultipleSelectOption,
1616
OperatorType,
1717
SortDirection,
18+
unsubscribeAllObservables,
1819
} from './../modules/angular-slickgrid';
1920
import * as moment from 'moment-mini';
2021
import { Subscription } from 'rxjs';
@@ -43,6 +44,7 @@ export class GridGraphqlComponent implements OnInit, OnDestroy {
4344
<li>You can also preload a grid with certain "presets" like Filters / Sorters / Pagination <a href="https://github.com/ghiscoding/Angular-Slickgrid/wiki/Grid-State-&-Preset" target="_blank">Wiki - Grid Preset</a>
4445
</ul>
4546
`;
47+
private subscriptions: Subscription[] = [];
4648
angularGrid: AngularGridInstance;
4749
columnDefinitions: Column[];
4850
gridOptions: GridOption;
@@ -54,7 +56,6 @@ export class GridGraphqlComponent implements OnInit, OnDestroy {
5456
status = { text: 'processing...', class: 'alert alert-danger' };
5557
isWithCursor = false;
5658
selectedLanguage: string;
57-
gridStateSub: Subscription;
5859

5960
constructor(private translate: TranslateService) {
6061
// always start with English for Cypress E2E tests to be consistent
@@ -64,7 +65,8 @@ export class GridGraphqlComponent implements OnInit, OnDestroy {
6465
}
6566

6667
ngOnDestroy() {
67-
this.gridStateSub.unsubscribe();
68+
// also unsubscribe all Angular Subscriptions
69+
this.subscriptions = unsubscribeAllObservables(this.subscriptions);
6870
}
6971

7072
ngOnInit(): void {
@@ -208,7 +210,9 @@ export class GridGraphqlComponent implements OnInit, OnDestroy {
208210

209211
angularGridReady(angularGrid: AngularGridInstance) {
210212
this.angularGrid = angularGrid;
211-
this.gridStateSub = this.angularGrid.gridStateService.onGridStateChanged.subscribe((data) => console.log(data));
213+
this.subscriptions.push(
214+
this.angularGrid.gridStateService.onGridStateChanged.subscribe((data) => console.log(data))
215+
);
212216
}
213217

214218
displaySpinner(isProcessing) {
@@ -294,8 +298,10 @@ export class GridGraphqlComponent implements OnInit, OnDestroy {
294298

295299
switchLanguage() {
296300
const nextLanguage = (this.selectedLanguage === 'en') ? 'fr' : 'en';
297-
this.translate.use(nextLanguage).subscribe(() => {
298-
this.selectedLanguage = nextLanguage;
299-
});
301+
this.subscriptions.push(
302+
this.translate.use(nextLanguage).subscribe(() => {
303+
this.selectedLanguage = nextLanguage;
304+
})
305+
);
300306
}
301307
}

src/app/examples/grid-headermenu.component.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
2-
import { AngularGridInstance, Column, GridOption } from './../modules/angular-slickgrid';
1+
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
2+
import { AngularGridInstance, Column, GridOption, unsubscribeAllObservables } from './../modules/angular-slickgrid';
33
import { TranslateService } from '@ngx-translate/core';
4+
import { Subscription } from 'rxjs';
45

56
@Component({
67
templateUrl: './grid-headermenu.component.html',
78
styleUrls: ['./grid-headermenu.component.scss'],
89
encapsulation: ViewEncapsulation.None,
910
})
10-
export class GridHeaderMenuComponent implements OnInit {
11+
export class GridHeaderMenuComponent implements OnInit, OnDestroy {
1112
title = 'Example 8: Header Menu Plugin';
1213
subTitle = `
1314
This example demonstrates using the <b>Slick.Plugins.HeaderMenu</b> plugin to easily add menus to colum headers.<br/>
@@ -29,6 +30,7 @@ export class GridHeaderMenuComponent implements OnInit {
2930
</ul>
3031
`;
3132

33+
private subscriptions: Subscription[] = [];
3234
angularGrid: AngularGridInstance;
3335
columnDefinitions: Column[];
3436
gridOptions: GridOption;
@@ -39,6 +41,11 @@ export class GridHeaderMenuComponent implements OnInit {
3941
this.selectedLanguage = this.translate.getDefaultLang();
4042
}
4143

44+
ngOnDestroy() {
45+
// also unsubscribe all Angular Subscriptions
46+
this.subscriptions = unsubscribeAllObservables(this.subscriptions);
47+
}
48+
4249
ngOnInit(): void {
4350
this.columnDefinitions = [
4451
{ id: 'title', name: 'Title', field: 'title', nameKey: 'TITLE' },
@@ -143,8 +150,10 @@ export class GridHeaderMenuComponent implements OnInit {
143150

144151
switchLanguage() {
145152
const nextLanguage = (this.selectedLanguage === 'en') ? 'fr' : 'en';
146-
this.translate.use(nextLanguage).subscribe(() => {
147-
this.selectedLanguage = nextLanguage;
148-
});
153+
this.subscriptions.push(
154+
this.translate.use(nextLanguage).subscribe(() => {
155+
this.selectedLanguage = nextLanguage;
156+
})
157+
);
149158
}
150159
}

src/app/examples/grid-localization.component.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Component, OnInit, Injectable } from '@angular/core';
1+
import { Component, OnDestroy, OnInit, Injectable } from '@angular/core';
22
import { TranslateService } from '@ngx-translate/core';
3+
import { Subscription } from 'rxjs';
34
import {
45
AngularGridInstance,
56
Column,
@@ -10,7 +11,8 @@ import {
1011
Formatter,
1112
Formatters,
1213
GridOption,
13-
GridStateChange
14+
GridStateChange,
15+
unsubscribeAllObservables
1416
} from './../modules/angular-slickgrid';
1517

1618
const NB_ITEMS = 1500;
@@ -27,7 +29,7 @@ const taskTranslateFormatter: Formatter = (row: number, cell: number, value: any
2729
templateUrl: './grid-localization.component.html'
2830
})
2931
@Injectable()
30-
export class GridLocalizationComponent implements OnInit {
32+
export class GridLocalizationComponent implements OnInit, OnDestroy {
3133
title = 'Example 12: Localization (i18n)';
3234
subTitle = `Support multiple locales with the ngx-translate plugin, following these steps (<a href="https://github.com/ghiscoding/Angular-Slickgrid/wiki/Localization" target="_blank">Wiki docs</a>)
3335
<ol class="small">
@@ -55,6 +57,7 @@ export class GridLocalizationComponent implements OnInit {
5557
</ol>
5658
`;
5759

60+
private subscriptions: Subscription[] = [];
5861
angularGrid: AngularGridInstance;
5962
columnDefinitions: Column[];
6063
gridOptions: GridOption;
@@ -70,6 +73,11 @@ export class GridLocalizationComponent implements OnInit {
7073
this.selectedLanguage = defaultLang;
7174
}
7275

76+
ngOnDestroy() {
77+
// also unsubscribe all Angular Subscriptions
78+
this.subscriptions = unsubscribeAllObservables(this.subscriptions);
79+
}
80+
7381
ngOnInit(): void {
7482
this.columnDefinitions = [
7583
{
@@ -266,8 +274,10 @@ export class GridLocalizationComponent implements OnInit {
266274

267275
switchLanguage() {
268276
const nextLanguage = (this.selectedLanguage === 'en') ? 'fr' : 'en';
269-
this.translate.use(nextLanguage).subscribe(() => {
270-
this.selectedLanguage = nextLanguage;
271-
});
277+
this.subscriptions.push(
278+
this.translate.use(nextLanguage).subscribe(() => {
279+
this.selectedLanguage = nextLanguage;
280+
})
281+
);
272282
}
273283
}

0 commit comments

Comments
 (0)