Skip to content

Commit cac70ac

Browse files
committed
perf(cdk/a11y): avoid triggering change detection if there are no subscribers to stream
Currently we have an `NgZone.run` call on each `focus` and `blur` event of a monitored event in order to bring its subscribers into the `NgZone`, however this means that we're also triggering change detection to any consumers that aren't subscribed to changes (e.g. `mat-button` only cares about the classes being applied). These changes move around some logic so that the `NgZone.run` is only hit if somebody has subscribed to the observable.
1 parent 2646ce9 commit cac70ac

File tree

1 file changed

+21
-11
lines changed

1 file changed

+21
-11
lines changed

src/cdk/a11y/focus-monitor/focus-monitor.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
Output,
2121
AfterViewInit,
2222
} from '@angular/core';
23-
import {Observable, of as observableOf, Subject, Subscription} from 'rxjs';
23+
import {Observable, of as observableOf, Subject, Subscription, Observer} from 'rxjs';
2424
import {coerceElement} from '@angular/cdk/coercion';
2525
import {DOCUMENT} from '@angular/common';
2626
import {
@@ -72,7 +72,8 @@ export const FOCUS_MONITOR_DEFAULT_OPTIONS =
7272
type MonitoredElementInfo = {
7373
checkChildren: boolean,
7474
readonly subject: Subject<FocusOrigin>,
75-
rootNode: HTMLElement|ShadowRoot|Document
75+
rootNode: HTMLElement|ShadowRoot|Document,
76+
observable: Observable<FocusOrigin>
7677
};
7778

7879
/**
@@ -260,15 +261,28 @@ export class FocusMonitor implements OnDestroy {
260261
}
261262

262263
// Create monitored element info.
264+
const subject = new Subject<FocusOrigin>();
263265
const info: MonitoredElementInfo = {
264266
checkChildren: checkChildren,
265-
subject: new Subject<FocusOrigin>(),
266-
rootNode
267+
subject,
268+
rootNode,
269+
// Note that we want the observable to emit inside the NgZone, however we don't want to
270+
// trigger change detection if nobody has subscribed to it. We do so by creating the
271+
// observable manually.
272+
observable: new Observable((observer: Observer<FocusOrigin>) => {
273+
const subscription = subject.subscribe(origin => {
274+
this._ngZone.run(() => observer.next(origin));
275+
});
276+
277+
return () => {
278+
subscription.unsubscribe();
279+
};
280+
})
267281
};
268282
this._elementInfo.set(nativeElement, info);
269283
this._registerGlobalListeners(info);
270284

271-
return info.subject;
285+
return info.observable;
272286
}
273287

274288
/**
@@ -474,11 +488,7 @@ export class FocusMonitor implements OnDestroy {
474488
}
475489

476490
this._setClasses(element);
477-
this._emitOrigin(elementInfo.subject, null);
478-
}
479-
480-
private _emitOrigin(subject: Subject<FocusOrigin>, origin: FocusOrigin) {
481-
this._ngZone.run(() => subject.next(origin));
491+
elementInfo.subject.next(null);
482492
}
483493

484494
private _registerGlobalListeners(elementInfo: MonitoredElementInfo) {
@@ -560,7 +570,7 @@ export class FocusMonitor implements OnDestroy {
560570
private _originChanged(element: HTMLElement, origin: FocusOrigin,
561571
elementInfo: MonitoredElementInfo) {
562572
this._setClasses(element, origin);
563-
this._emitOrigin(elementInfo.subject, origin);
573+
elementInfo.subject.next(origin);
564574
this._lastFocusOrigin = origin;
565575
}
566576

0 commit comments

Comments
 (0)