Skip to content

Commit

Permalink
perf(cdk/a11y): avoid triggering change detection if there are no sub…
Browse files Browse the repository at this point in the history
…scribers 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.
  • Loading branch information
crisbeto committed May 7, 2021
1 parent 2646ce9 commit cac70ac
Showing 1 changed file with 21 additions and 11 deletions.
32 changes: 21 additions & 11 deletions src/cdk/a11y/focus-monitor/focus-monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
Output,
AfterViewInit,
} 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';
import {DOCUMENT} from '@angular/common';
import {
Expand Down Expand Up @@ -72,7 +72,8 @@ export const FOCUS_MONITOR_DEFAULT_OPTIONS =
type MonitoredElementInfo = {
checkChildren: boolean,
readonly subject: Subject<FocusOrigin>,
rootNode: HTMLElement|ShadowRoot|Document
rootNode: HTMLElement|ShadowRoot|Document,
observable: Observable<FocusOrigin>
};

/**
Expand Down Expand Up @@ -260,15 +261,28 @@ export class FocusMonitor implements OnDestroy {
}

// Create monitored element info.
const subject = new Subject<FocusOrigin>();
const info: MonitoredElementInfo = {
checkChildren: checkChildren,
subject: new Subject<FocusOrigin>(),
rootNode
subject,
rootNode,
// 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<FocusOrigin>) => {
const subscription = subject.subscribe(origin => {
this._ngZone.run(() => observer.next(origin));
});

return () => {
subscription.unsubscribe();
};
})
};
this._elementInfo.set(nativeElement, info);
this._registerGlobalListeners(info);

return info.subject;
return info.observable;
}

/**
Expand Down Expand Up @@ -474,11 +488,7 @@ export class FocusMonitor implements OnDestroy {
}

this._setClasses(element);
this._emitOrigin(elementInfo.subject, null);
}

private _emitOrigin(subject: Subject<FocusOrigin>, origin: FocusOrigin) {
this._ngZone.run(() => subject.next(origin));
elementInfo.subject.next(null);
}

private _registerGlobalListeners(elementInfo: MonitoredElementInfo) {
Expand Down Expand Up @@ -560,7 +570,7 @@ export class FocusMonitor implements OnDestroy {
private _originChanged(element: HTMLElement, origin: FocusOrigin,
elementInfo: MonitoredElementInfo) {
this._setClasses(element, origin);
this._emitOrigin(elementInfo.subject, origin);
elementInfo.subject.next(origin);
this._lastFocusOrigin = origin;
}

Expand Down

0 comments on commit cac70ac

Please sign in to comment.