diff --git a/src/cdk/a11y/focus-monitor/focus-monitor.ts b/src/cdk/a11y/focus-monitor/focus-monitor.ts index 58f207e2a47f..e7befafe4422 100644 --- a/src/cdk/a11y/focus-monitor/focus-monitor.ts +++ b/src/cdk/a11y/focus-monitor/focus-monitor.ts @@ -18,7 +18,7 @@ import { Output, SkipSelf, } from '@angular/core'; -import {Observable, of as observableOf, Subject, Subscription} from 'rxjs'; +import {Observable, of as observableOf, Subject, Subscription, Observer} from 'rxjs'; import {coerceElement} from '@angular/cdk/coercion'; @@ -41,7 +41,8 @@ export interface FocusOptions { type MonitoredElementInfo = { unlisten: Function, checkChildren: boolean, - subject: Subject + subject: Subject, + observable: Observable }; /** @@ -169,17 +170,30 @@ export class FocusMonitor implements OnDestroy { } // Create monitored element info. - let info: MonitoredElementInfo = { + const subject = new Subject(); + const info: MonitoredElementInfo = { unlisten: () => {}, - checkChildren: checkChildren, - subject: new Subject() + checkChildren, + subject, + // Note that we want the observable to emit inside the NgZone, however we don't want to + // trigger change detection if nobody has subscribed to it. We do so by creating the + // observable manually. + observable: new Observable((observer: Observer) => { + const subscription = subject.subscribe(origin => { + this._ngZone.run(() => observer.next(origin)); + }); + + return () => { + subscription.unsubscribe(); + }; + }) }; this._elementInfo.set(nativeElement, info); this._incrementMonitoredElementCount(); // Start listening. We need to listen in capture phase since focus events don't bubble. - let focusListener = (event: FocusEvent) => this._onFocus(event, nativeElement); - let blurListener = (event: FocusEvent) => this._onBlur(event, nativeElement); + const focusListener = (event: FocusEvent) => this._onFocus(event, nativeElement); + const blurListener = (event: FocusEvent) => this._onBlur(event, nativeElement); this._ngZone.runOutsideAngular(() => { nativeElement.addEventListener('focus', focusListener, true); nativeElement.addEventListener('blur', blurListener, true); @@ -191,7 +205,7 @@ export class FocusMonitor implements OnDestroy { nativeElement.removeEventListener('blur', blurListener, true); }; - return info.subject.asObservable(); + return info.observable; } /** @@ -358,7 +372,7 @@ export class FocusMonitor implements OnDestroy { } this._setClasses(element, origin); - this._emitOrigin(elementInfo.subject, origin); + elementInfo.subject.next(origin); this._lastFocusOrigin = origin; } @@ -378,11 +392,7 @@ export class FocusMonitor implements OnDestroy { } this._setClasses(element); - this._emitOrigin(elementInfo.subject, null); - } - - private _emitOrigin(subject: Subject, origin: FocusOrigin) { - this._ngZone.run(() => subject.next(origin)); + elementInfo.subject.next(null); } private _incrementMonitoredElementCount() {