Skip to content

Commit ea82f1d

Browse files
authored
feat(overlay): add detach method to overlay service #6063, #7088 (#8823)
1 parent 673a2e2 commit ea82f1d

27 files changed

+1677
-1404
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ All notable changes for each version of this project will be documented in this
6262

6363

6464

65+
### New Features
66+
- `IgxOverlayService`
67+
- `detach` and `detachAll` methods are added to `IgxOverlayService`. Calling `detach` will remove all the elements generated for the related overlay, as well as clean up all related resources. Calling `detachAll` will remove all elements generated by any call to `IgxOverlay` `attach`, and will clean up all related resources. _Note: calling `hide` or `hideAll` will not clean generated by the service elements and will not clean up related resources._
68+
69+
6570
## 11.1.1
6671

6772
### New Features

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

+8-3
Original file line numberDiff line numberDiff line change
@@ -2045,7 +2045,6 @@ describe('igxCombo', () => {
20452045
fixture.detectChanges();
20462046
expect(document.activeElement).toEqual(combo.searchInput.nativeElement);
20472047
}));
2048-
20492048
it('should properly add items to the defaultFallbackGroup', () => {
20502049
combo.allowCustomValues = true;
20512050
combo.toggle();
@@ -2452,22 +2451,26 @@ describe('igxCombo', () => {
24522451
expect(combo.collapsed).toBeFalsy();
24532452
expect(combo.value).toEqual('My New Custom Item');
24542453
});
2455-
it('should enable/disable filtering at runtime', () => {
2454+
it('should enable/disable filtering at runtime', fakeAsync(() => {
24562455
combo.open(); // Open combo - all data items are in filteredData
2456+
tick();
24572457
fixture.detectChanges();
24582458
expect(combo.dropdown.items.length).toBeGreaterThan(0);
24592459

24602460
const searchInput = fixture.debugElement.query(By.css(CSS_CLASS_SEARCHINPUT));
24612461
searchInput.nativeElement.value = 'Not-available item';
24622462
searchInput.triggerEventHandler('input', { target: searchInput.nativeElement });
2463+
tick();
24632464
fixture.detectChanges();
24642465
expect(combo.dropdown.items.length).toEqual(0); // No items are available because of filtering
24652466

24662467
combo.close(); // Filter is cleared on close
2468+
tick();
24672469
fixture.detectChanges();
24682470
combo.filterable = false; // Filtering is disabled
24692471
fixture.detectChanges();
24702472
combo.open(); // All items are visible since filtering is disabled
2473+
tick();
24712474
fixture.detectChanges();
24722475
expect(combo.dropdown.items.length).toBeGreaterThan(0); // All items are visible since filtering is disabled
24732476

@@ -2477,13 +2480,15 @@ describe('igxCombo', () => {
24772480
expect(combo.dropdown.items.length).toBeGreaterThan(0); // All items are visible since filtering is disabled
24782481

24792482
combo.close(); // Filter is cleared on close
2483+
tick();
24802484
fixture.detectChanges();
24812485
combo.filterable = true; // Filtering is re-enabled
24822486
fixture.detectChanges();
24832487
combo.open(); // Filter is cleared on open
2488+
tick();
24842489
fixture.detectChanges();
24852490
expect(combo.dropdown.items.length).toBeGreaterThan(0);
2486-
});
2491+
}));
24872492
it(`should properly display "Add Item" button when filtering is off`, () => {
24882493
combo.allowCustomValues = true;
24892494
combo.filterable = false;

projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts

+36-31
Original file line numberDiff line numberDiff line change
@@ -246,16 +246,16 @@ describe('IgxDatePicker', () => {
246246

247247
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper--modal');
248248
expect(overlayToggle[0]).not.toBeNull();
249-
expect(overlayToggle[0]).not.toBeUndefined();
249+
expect(overlayToggle[0]).toBeDefined();
250250

251251
UIInteractions.triggerKeyDownEvtUponElem('Escape', overlayToggle[0], true);
252252
flush();
253253
fixture.detectChanges();
254254

255255
const input = fixture.debugElement.query(By.directive(IgxInputDirective)).nativeElement;
256-
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper--modal');
257-
expect(overlayToggle[0]).toEqual(undefined);
258256
expect(input).toEqual(document.activeElement);
257+
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper--modal');
258+
expect(overlayToggle[0]).toBeUndefined();
259259
}));
260260

261261
it('When a modal datepicker is closed via outside click, the focus should remain on the input',
@@ -270,16 +270,16 @@ describe('IgxDatePicker', () => {
270270

271271
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper--modal');
272272
expect(overlayToggle[0]).not.toBeNull();
273-
expect(overlayToggle[0]).not.toBeUndefined();
273+
expect(overlayToggle[0]).toBeDefined();
274274

275275
UIInteractions.simulateClickAndSelectEvent(overlayToggle[0]);
276276
flush();
277277
fixture.detectChanges();
278278

279279
const input = fixture.debugElement.query(By.directive(IgxInputDirective)).nativeElement;
280-
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper--modal');
281-
expect(overlayToggle[0]).toEqual(undefined);
282280
expect(input).toEqual(document.activeElement);
281+
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper--modal');
282+
expect(overlayToggle[0]).toBeUndefined();
283283
}));
284284

285285
it('When datepicker is closed upon selecting a date, the focus should remain on the input',
@@ -294,7 +294,7 @@ describe('IgxDatePicker', () => {
294294

295295
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper--modal');
296296
expect(overlayToggle[0]).not.toBeNull();
297-
expect(overlayToggle[0]).not.toBeUndefined();
297+
expect(overlayToggle[0]).toBeDefined();
298298

299299
// select a date
300300
const dateElemToSelect = document.getElementsByClassName('igx-calendar__date-content')[10];
@@ -303,17 +303,17 @@ describe('IgxDatePicker', () => {
303303
fixture.detectChanges();
304304

305305
const input = fixture.debugElement.query(By.directive(IgxInputDirective)).nativeElement;
306-
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper--modal');
307-
expect(overlayToggle[0]).toEqual(undefined);
308306
expect(input).toEqual(document.activeElement);
307+
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper--modal');
308+
expect(overlayToggle[0]).toBeUndefined();
309309
}));
310310

311-
it('should allow setting editorTabIndex', () => {
312-
fixture.componentInstance.datePicker.editorTabIndex = 3;
313-
fixture.detectChanges();
314-
const input = fixture.debugElement.query(By.directive(IgxInputDirective)).nativeElement;
315-
expect(input.tabIndex).toBe(3);
316-
});
311+
it('should allow setting editorTabIndex', () => {
312+
fixture.componentInstance.datePicker.editorTabIndex = 3;
313+
fixture.detectChanges();
314+
const input = fixture.debugElement.query(By.directive(IgxInputDirective)).nativeElement;
315+
expect(input.tabIndex).toBe(3);
316+
});
317317
});
318318

319319
describe('ARIA Tests', () => {
@@ -341,7 +341,6 @@ describe('IgxDatePicker', () => {
341341
expect(inputLabelledBy).toEqual(labelID);
342342
});
343343

344-
345344
it('ARIA Test for picker with a dropdown mode', () => {
346345
const fixture = TestBed.createComponent(IgxDatePickerOpeningComponent);
347346
fixture.detectChanges();
@@ -411,8 +410,8 @@ describe('IgxDatePicker', () => {
411410
fixture.detectChanges();
412411

413412
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
414-
expect(overlayToggle[0]).not.toBeNull();
415-
expect(overlayToggle[0]).not.toBeUndefined();
413+
expect(overlayToggle).not.toBeNull();
414+
expect(overlayToggle).toBeDefined();
416415

417416
const dummyInput = fixture.componentInstance.dummyInput.nativeElement;
418417
dummyInput.focus();
@@ -421,7 +420,7 @@ describe('IgxDatePicker', () => {
421420
fixture.detectChanges();
422421

423422
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
424-
expect(overlayToggle[0]).toEqual(undefined);
423+
expect(overlayToggle[0]).toBeUndefined();
425424
expect(input).not.toEqual(document.activeElement);
426425
expect(dummyInput).toEqual(document.activeElement);
427426
}));
@@ -448,43 +447,46 @@ describe('IgxDatePicker', () => {
448447
tick();
449448
fixture.detectChanges();
450449
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
451-
expect(overlayToggle[0]).toEqual(undefined);
450+
expect(overlayToggle[0]).toBeUndefined();
452451
expect(input).toEqual(document.activeElement);
453452

454453
// Cancel btn
455454
UIInteractions.triggerKeyDownEvtUponElem('space', datePickerDom.nativeElement, false);
456455
flush();
457456
fixture.detectChanges();
457+
expect(buttons.length).toEqual(2);
458458
const cancelBtn = buttons[0] as HTMLElement;
459459
expect(cancelBtn.innerText).toBe('Cancel');
460460
cancelBtn.click();
461461
tick();
462462
fixture.detectChanges();
463463
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
464-
expect(overlayToggle[0]).toEqual(undefined);
464+
expect(overlayToggle[0]).toBeUndefined();
465465
expect(input).toEqual(document.activeElement);
466466

467467
// Enter key
468468
UIInteractions.triggerKeyDownEvtUponElem('space', datePickerDom.nativeElement, false);
469469
flush();
470470
fixture.detectChanges();
471+
expect(buttons.length).toEqual(2);
471472
document.activeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
472473
tick();
473474
fixture.detectChanges();
474475
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
475-
expect(overlayToggle[0]).toEqual(undefined);
476+
expect(overlayToggle[0]).toBeUndefined();
476477
expect(input).toEqual(document.activeElement);
477478

478479
// Esc key
479480
UIInteractions.triggerKeyDownEvtUponElem('space', datePickerDom.nativeElement, false);
480481
flush();
481482
fixture.detectChanges();
483+
expect(buttons.length).toEqual(2);
482484
document.activeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
483485
tick();
484486
fixture.detectChanges();
485487

486488
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
487-
expect(overlayToggle[0]).toEqual(undefined);
489+
expect(overlayToggle[0]).toBeUndefined();
488490
expect(input).toEqual(document.activeElement);
489491
}));
490492

@@ -1235,7 +1237,6 @@ describe('IgxDatePicker', () => {
12351237
expect(datePicker.onDisabledDate.emit).toHaveBeenCalledTimes(2);
12361238
}));
12371239

1238-
12391240
it('should stop spinning on max/min when isSpinLoop is set to false - dropdown mode', fakeAsync(() => {
12401241
const input = fixture.debugElement.query(By.directive(IgxInputDirective));
12411242
expect(input).toBeDefined();
@@ -1422,8 +1423,10 @@ describe('IgxDatePicker', () => {
14221423
fixture.detectChanges();
14231424
expect(inputDirective.valid).toEqual(IgxInputState.INITIAL);
14241425

1425-
datePickerOnChangeComponent.disabledDates = [{ type: DateRangeType.Before,
1426-
dateRange: [new Date(today.getFullYear(), today.getMonth(), today.getDate() + 2)] }];
1426+
datePickerOnChangeComponent.disabledDates = [{
1427+
type: DateRangeType.Before,
1428+
dateRange: [new Date(today.getFullYear(), today.getMonth(), today.getDate() + 2)]
1429+
}];
14271430
fixture.detectChanges();
14281431
expect(inputDirective.valid).toEqual(IgxInputState.INVALID);
14291432
}));
@@ -1441,16 +1444,18 @@ describe('IgxDatePicker', () => {
14411444
fixture.detectChanges();
14421445
expect(inputDirective.valid).toEqual(IgxInputState.INITIAL);
14431446

1444-
datePickerTemplateIGComponent.disabledDates = [{ type: DateRangeType.Before,
1445-
dateRange: [new Date(today.getFullYear(), today.getMonth(), today.getDate() + 2)] }];
1447+
datePickerTemplateIGComponent.disabledDates = [{
1448+
type: DateRangeType.Before,
1449+
dateRange: [new Date(today.getFullYear(), today.getMonth(), today.getDate() + 2)]
1450+
}];
14461451
fixture.detectChanges();
14471452
expect(inputDirective.valid).toEqual(IgxInputState.INVALID);
14481453
}));
14491454

14501455
it('Should set date picker status to invalid on blur when pass or change a template', fakeAsync(() => {
14511456
datePickerTemplateIGComponent.disabledDates = [{ type: DateRangeType.Before, dateRange: [new Date()] }];
14521457
const templateInputDirective = datePickerTemplateIGComponent.inputDirective;
1453-
const templateInput = templateInputDirective.nativeElement;
1458+
const templateInput = templateInputDirective.nativeElement;
14541459
templateInput.dispatchEvent(new Event('blur'));
14551460
fixture.detectChanges();
14561461
expect(templateInputDirective.valid).toEqual(IgxInputState.INVALID);
@@ -1459,7 +1464,7 @@ describe('IgxDatePicker', () => {
14591464
fixture.detectChanges();
14601465
// obtain the default template input directive & input
14611466
const inputDirective = datePickerTemplateIGComponent.inputDirective;
1462-
const input = inputDirective.nativeElement;
1467+
const input = inputDirective.nativeElement;
14631468
expect(inputDirective.valid).toEqual(IgxInputState.INITIAL);
14641469

14651470
input.dispatchEvent(new Event('blur'));
@@ -1668,7 +1673,7 @@ export class IgxDatePickerNgModelComponent {
16681673
})
16691674
export class IgxDatePickerRetemplatedComponent {
16701675
@ViewChild(IgxDatePickerComponent, { static: true }) public datePicker: IgxDatePickerComponent;
1671-
}
1676+
}
16721677

16731678
@Component({
16741679
template: `

projects/igniteui-angular/src/lib/date-picker/date-picker.component.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -997,7 +997,7 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
997997
/** @hidden @internal */
998998
public ngOnDestroy(): void {
999999
if (this._componentID) {
1000-
this._overlayService.hide(this._componentID);
1000+
this._overlayService.detach(this._componentID);
10011001
}
10021002
if (this._statusChanges$) {
10031003
this._statusChanges$.unsubscribe();
@@ -1071,7 +1071,9 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
10711071
if (!this.collapsed || this.disabled) {
10721072
return;
10731073
}
1074-
1074+
if (this._componentID) {
1075+
this._overlayService.detach(this._componentID);
1076+
}
10751077
switch (this.mode) {
10761078
case InteractionMode.Dialog: {
10771079
this.hasHeader = true;
@@ -1361,6 +1363,7 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
13611363
}
13621364

13631365
private _onClosed(): void {
1366+
this._overlayService.detach(this._componentID);
13641367
this.collapsed = true;
13651368
this._componentID = null;
13661369
this.onClosed.emit(this);

projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.spec.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,16 @@ describe('IgxToggle', () => {
8888
tick();
8989
fixture.detectChanges();
9090

91-
expect(toggle.onAppended.emit).toHaveBeenCalled();
91+
expect(toggle.onAppended.emit).toHaveBeenCalledTimes(1);
92+
93+
toggle.close();
94+
tick();
95+
fixture.detectChanges();
96+
toggle.open();
97+
tick();
98+
fixture.detectChanges();
99+
100+
expect(toggle.onAppended.emit).toHaveBeenCalledTimes(2);
92101
}));
93102

94103
it('should emit \'onClosing\' and \'onClosed\' events', fakeAsync(() => {
@@ -106,8 +115,8 @@ describe('IgxToggle', () => {
106115
tick();
107116
fixture.detectChanges();
108117

109-
expect(toggle.onClosing.emit).toHaveBeenCalled();
110-
expect(toggle.onClosed.emit).toHaveBeenCalled();
118+
expect(toggle.onClosing.emit).toHaveBeenCalledTimes(1);
119+
expect(toggle.onClosed.emit).toHaveBeenCalledTimes(1);
111120
}));
112121

113122
it('should propagate IgxOverlay onOpened/onClosed events', fakeAsync(() => {

0 commit comments

Comments
 (0)