From 8972bf4e795862d04162cb81263868d999319636 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 23 Jan 2018 16:57:22 +0100 Subject: [PATCH] fix(focus-monitor): implement OnDestroy logic (#9305) Similarly to most of the other DOM-related services, these changes implement `ngOnDestroy` for the `FocusMonitor` which removes the monitoring from all of the currently-monitored elements. --- src/cdk/a11y/focus-monitor.spec.ts | 18 ++++++++++++++++-- src/cdk/a11y/focus-monitor.ts | 12 ++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/cdk/a11y/focus-monitor.spec.ts b/src/cdk/a11y/focus-monitor.spec.ts index ecc40ad2af3..7ae9dc08330 100644 --- a/src/cdk/a11y/focus-monitor.spec.ts +++ b/src/cdk/a11y/focus-monitor.spec.ts @@ -186,14 +186,28 @@ describe('FocusMonitor', () => { fixture.detectChanges(); tick(); - expect(buttonElement.classList.length) - .toBe(2, 'button should have exactly 2 focus classes'); + expect(buttonElement.classList.length).toBe(2, 'button should have exactly 2 focus classes'); focusMonitor.stopMonitoring(buttonElement); fixture.detectChanges(); expect(buttonElement.classList.length).toBe(0, 'button should not have any focus classes'); })); + + it('should remove classes when destroyed', fakeAsync(() => { + buttonElement.focus(); + fixture.detectChanges(); + tick(); + + expect(buttonElement.classList.length).toBe(2, 'button should have exactly 2 focus classes'); + + // Destroy manually since destroying the fixture won't do it. + focusMonitor.ngOnDestroy(); + fixture.detectChanges(); + + expect(buttonElement.classList.length).toBe(0, 'button should not have any focus classes'); + })); + }); diff --git a/src/cdk/a11y/focus-monitor.ts b/src/cdk/a11y/focus-monitor.ts index 67e51f85def..dbd858e1cd7 100644 --- a/src/cdk/a11y/focus-monitor.ts +++ b/src/cdk/a11y/focus-monitor.ts @@ -42,7 +42,7 @@ type MonitoredElementInfo = { /** Monitors mouse and keyboard events to determine the cause of focus events. */ @Injectable() -export class FocusMonitor { +export class FocusMonitor implements OnDestroy { /** The focus origin that the next focus event is a result of. */ private _origin: FocusOrigin = null; @@ -58,8 +58,8 @@ export class FocusMonitor { /** The timeout id of the touch timeout, used to cancel timeout later. */ private _touchTimeout: number; - /** Weak map of elements being monitored to their info. */ - private _elementInfo = new WeakMap(); + /** Map of elements being monitored to their info. */ + private _elementInfo = new Map(); /** A map of global objects to lists of current listeners. */ private _unregisterGlobalListeners = () => {}; @@ -135,7 +135,7 @@ export class FocusMonitor { * @param element The element to stop monitoring. */ stopMonitoring(element: HTMLElement): void { - let elementInfo = this._elementInfo.get(element); + const elementInfo = this._elementInfo.get(element); if (elementInfo) { elementInfo.unlisten(); @@ -157,6 +157,10 @@ export class FocusMonitor { element.focus(); } + ngOnDestroy() { + this._elementInfo.forEach((_info, element) => this.stopMonitoring(element)); + } + /** Register necessary event listeners on the document and window. */ private _registerGlobalListeners() { // Do nothing if we're not on the browser platform.