Skip to content

Commit 2d6f238

Browse files
teodosiahLipata
andauthored
fix(select): fix memory leak in IgxSelectionAPIService (#13929)
Co-authored-by: Nikolay Alipiev <[email protected]>
1 parent 240c006 commit 2d6f238

File tree

8 files changed

+46
-18
lines changed

8 files changed

+46
-18
lines changed

projects/igniteui-angular/src/lib/combo/combo.common.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1025,7 +1025,7 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement
10251025
this.destroy$.next();
10261026
this.destroy$.complete();
10271027
this.comboAPI.clear();
1028-
this.selectionService.clear(this.id);
1028+
this.selectionService.delete(this.id);
10291029
}
10301030

10311031
/**

projects/igniteui-angular/src/lib/combo/combo.component.spec.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ describe('igxCombo', () => {
8080
const elementRef = { nativeElement: null };
8181
const mockSelection: {
8282
[key: string]: jasmine.Spy;
83-
} = jasmine.createSpyObj('IgxSelectionAPIService', ['get', 'set', 'add_items', 'select_items']);
83+
} = jasmine.createSpyObj('IgxSelectionAPIService', ['get', 'set', 'add_items', 'select_items', 'delete']);
8484
const mockCdr = jasmine.createSpyObj('ChangeDetectorRef', ['markForCheck', 'detectChanges']);
85-
const mockComboService = jasmine.createSpyObj('IgxComboAPIService', ['register']);
85+
const mockComboService = jasmine.createSpyObj('IgxComboAPIService', ['register', 'clear']);
8686
const mockNgControl = jasmine.createSpyObj('NgControl', ['registerOnChangeCb', 'registerOnTouchedCb']);
8787
const mockInjector = jasmine.createSpyObj('Injector', {
8888
get: mockNgControl
@@ -801,6 +801,13 @@ describe('igxCombo', () => {
801801
expect([...selectionService.get(combo.id)][1]).toBe(subParams.newValue);
802802
sub.unsubscribe();
803803
}));
804+
it('should delete the selection on destroy', () => {
805+
combo = new IgxComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService,
806+
mockIconService, null, null, mockInjector);
807+
combo.ngOnDestroy();
808+
expect(mockComboService.clear).toHaveBeenCalled();
809+
expect(mockSelection.delete).toHaveBeenCalled();
810+
});
804811
});
805812

806813
describe('Combo feature tests: ', () => {

projects/igniteui-angular/src/lib/core/selection.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ export class IgxSelectionAPIService {
4242
this.selection.set(componentID, this.get_empty());
4343
}
4444

45+
/**
46+
* Removes selection for a component.
47+
* @param componentID
48+
*/
49+
public delete(componentID: string) {
50+
this.selection.delete(componentID);
51+
}
52+
4553
/**
4654
* Get current component selection length.
4755
*

projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe('IgxDropDown ', () => {
4444
{ value: 'Item5', index: 5 } as IgxDropDownItemComponent];
4545
const mockSelection: {
4646
[key: string]: jasmine.Spy;
47-
} = jasmine.createSpyObj('IgxSelectionAPIService', ['get', 'set', 'add_items', 'select_items']);
47+
} = jasmine.createSpyObj('IgxSelectionAPIService', ['get', 'set', 'add_items', 'select_items', 'delete']);
4848
const mockCdr = jasmine.createSpyObj('ChangeDetectorRef', ['markForCheck', 'detectChanges']);
4949
mockSelection.get.and.returnValue(new Set([]));
5050
const mockForOf = jasmine.createSpyObj('IgxForOfDirective', ['totalItemCount']);
@@ -179,6 +179,13 @@ describe('IgxDropDown ', () => {
179179
dropdown.toggle();
180180
expect(dropdown.close).toHaveBeenCalledTimes(1);
181181
});
182+
it('should remove selection on destroy', () => {
183+
const selectionService = new IgxSelectionAPIService();
184+
const selectionDeleteSpy = spyOn(selectionService, 'delete');
185+
dropdown = new IgxDropDownComponent({ nativeElement: null }, mockCdr, selectionService, null);
186+
dropdown.ngOnDestroy();
187+
expect(selectionDeleteSpy).toHaveBeenCalled();
188+
});
182189
});
183190
describe('User interaction tests', () => {
184191
describe('Selection & key navigation', () => {

projects/igniteui-angular/src/lib/drop-down/drop-down.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,8 +409,8 @@ export class IgxDropDownComponent extends IgxDropDownBaseDirective implements ID
409409
public ngOnDestroy() {
410410
this.destroy$.next(true);
411411
this.destroy$.complete();
412-
this.selection.clear(this.id);
413-
this.selection.clear(`${this.id}-active`);
412+
this.selection.delete(this.id);
413+
this.selection.delete(`${this.id}-active`);
414414
}
415415

416416
/** @hidden @internal */

projects/igniteui-angular/src/lib/select/select.component.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2666,7 +2666,7 @@ describe('igxSelect', () => {
26662666
describe('igxSelect ControlValueAccessor Unit', () => {
26672667
let select: IgxSelectComponent;
26682668
it('Should correctly implement interface methods', () => {
2669-
const mockSelection = jasmine.createSpyObj('IgxSelectionAPIService', ['get', 'set', 'clear', 'first_item']);
2669+
const mockSelection = jasmine.createSpyObj('IgxSelectionAPIService', ['get', 'set', 'clear', 'delete', 'first_item']);
26702670
const mockCdr = jasmine.createSpyObj('ChangeDetectorRef', ['detectChanges']);
26712671
const mockNgControl = jasmine.createSpyObj('NgControl', ['registerOnChangeCb', 'registerOnTouchedCb']);
26722672
const mockInjector = jasmine.createSpyObj('Injector', {
@@ -2707,6 +2707,10 @@ describe('igxSelect ControlValueAccessor Unit', () => {
27072707
spyOnProperty(select, 'collapsed').and.returnValue(true);
27082708
select.onBlur();
27092709
expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(2);
2710+
2711+
// destroy
2712+
select.ngOnDestroy();
2713+
expect(mockSelection.delete).toHaveBeenCalled();
27102714
});
27112715

27122716
it('Should correctly handle ngControl validity', () => {

projects/igniteui-angular/src/lib/select/select.component.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -550,15 +550,6 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
550550
}
551551
}
552552

553-
/**
554-
* @hidden @internal
555-
*/
556-
public override ngOnDestroy() {
557-
this.destroy$.next(true);
558-
this.destroy$.complete();
559-
this.selection.clear(this.id);
560-
}
561-
562553
/**
563554
* @hidden @internal
564555
* Prevent input blur - closing the items container on Header/Footer Template click.

projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ describe('IgxSimpleCombo', () => {
6666
const elementRef = { nativeElement: null };
6767
const mockSelection: {
6868
[key: string]: jasmine.Spy;
69-
} = jasmine.createSpyObj('IgxSelectionAPIService', ['get', 'set', 'add_items', 'select_items']);
69+
} = jasmine.createSpyObj('IgxSelectionAPIService', ['get', 'set', 'add_items', 'select_items', 'delete']);
7070
const mockCdr = jasmine.createSpyObj('ChangeDetectorRef', ['markForCheck', 'detectChanges']);
71-
const mockComboService = jasmine.createSpyObj('IgxComboAPIService', ['register']);
71+
const mockComboService = jasmine.createSpyObj('IgxComboAPIService', ['register', 'clear']);
7272
const mockNgControl = jasmine.createSpyObj('NgControl', ['registerOnChangeCb', 'registerOnTouchedCb']);
7373
const mockInjector = jasmine.createSpyObj('Injector', {
7474
get: mockNgControl
@@ -430,6 +430,17 @@ describe('IgxSimpleCombo', () => {
430430
combo.handleClear(spyObj);
431431
expect(combo.displayValue).toEqual([item[0]]);
432432
});
433+
434+
it('should delete the selection on destroy', () => {
435+
const selectionService = new IgxSelectionAPIService();
436+
const comboClearSpy = spyOn(mockComboService, 'clear');
437+
const selectionDeleteSpy = spyOn(selectionService, 'delete');
438+
combo = new IgxSimpleComboComponent(elementRef, mockCdr, selectionService, mockComboService,
439+
mockIconService, platformUtil, null, null, mockInjector);
440+
combo.ngOnDestroy();
441+
expect(comboClearSpy).toHaveBeenCalled();
442+
expect(selectionDeleteSpy).toHaveBeenCalled();
443+
});
433444
});
434445

435446
describe('Initialization and rendering tests: ', () => {

0 commit comments

Comments
 (0)