Skip to content

Commit 085bbb7

Browse files
crisbetommalerba
authored andcommitted
perf(focus-monitor): avoid triggering change detection if there are no subscribers to stream (#14964)
Currently we have an `NgZone.run` call on each `focus` and `blur` event of a monitored element 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 e039d63 commit 085bbb7

File tree

1 file changed

+24
-14
lines changed

1 file changed

+24
-14
lines changed

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

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
Output,
1919
SkipSelf,
2020
} from '@angular/core';
21-
import {Observable, of as observableOf, Subject, Subscription} from 'rxjs';
21+
import {Observable, of as observableOf, Subject, Subscription, Observer} from 'rxjs';
2222
import {coerceElement} from '@angular/cdk/coercion';
2323

2424

@@ -41,7 +41,8 @@ export interface FocusOptions {
4141
type MonitoredElementInfo = {
4242
unlisten: Function,
4343
checkChildren: boolean,
44-
subject: Subject<FocusOrigin>
44+
subject: Subject<FocusOrigin>,
45+
observable: Observable<FocusOrigin>
4546
};
4647

4748
/**
@@ -169,17 +170,30 @@ export class FocusMonitor implements OnDestroy {
169170
}
170171

171172
// Create monitored element info.
172-
let info: MonitoredElementInfo = {
173+
const subject = new Subject<FocusOrigin>();
174+
const info: MonitoredElementInfo = {
173175
unlisten: () => {},
174-
checkChildren: checkChildren,
175-
subject: new Subject<FocusOrigin>()
176+
checkChildren,
177+
subject,
178+
// Note that we want the observable to emit inside the NgZone, however we don't want to
179+
// trigger change detection if nobody has subscribed to it. We do so by creating the
180+
// observable manually.
181+
observable: new Observable((observer: Observer<FocusOrigin>) => {
182+
const subscription = subject.subscribe(origin => {
183+
this._ngZone.run(() => observer.next(origin));
184+
});
185+
186+
return () => {
187+
subscription.unsubscribe();
188+
};
189+
})
176190
};
177191
this._elementInfo.set(nativeElement, info);
178192
this._incrementMonitoredElementCount();
179193

180194
// Start listening. We need to listen in capture phase since focus events don't bubble.
181-
let focusListener = (event: FocusEvent) => this._onFocus(event, nativeElement);
182-
let blurListener = (event: FocusEvent) => this._onBlur(event, nativeElement);
195+
const focusListener = (event: FocusEvent) => this._onFocus(event, nativeElement);
196+
const blurListener = (event: FocusEvent) => this._onBlur(event, nativeElement);
183197
this._ngZone.runOutsideAngular(() => {
184198
nativeElement.addEventListener('focus', focusListener, true);
185199
nativeElement.addEventListener('blur', blurListener, true);
@@ -191,7 +205,7 @@ export class FocusMonitor implements OnDestroy {
191205
nativeElement.removeEventListener('blur', blurListener, true);
192206
};
193207

194-
return info.subject.asObservable();
208+
return info.observable;
195209
}
196210

197211
/**
@@ -358,7 +372,7 @@ export class FocusMonitor implements OnDestroy {
358372
}
359373

360374
this._setClasses(element, origin);
361-
this._emitOrigin(elementInfo.subject, origin);
375+
elementInfo.subject.next(origin);
362376
this._lastFocusOrigin = origin;
363377
}
364378

@@ -378,11 +392,7 @@ export class FocusMonitor implements OnDestroy {
378392
}
379393

380394
this._setClasses(element);
381-
this._emitOrigin(elementInfo.subject, null);
382-
}
383-
384-
private _emitOrigin(subject: Subject<FocusOrigin>, origin: FocusOrigin) {
385-
this._ngZone.run(() => subject.next(origin));
395+
elementInfo.subject.next(null);
386396
}
387397

388398
private _incrementMonitoredElementCount() {

0 commit comments

Comments
 (0)