Skip to content

Commit 01cc6f3

Browse files
committed
fix(cdk/overlay): only emit positionChanges when position is different
Currently we emit the `positionChanges` event whenever a position is recalculcated which can be on each scroll event. These changes switch to doing so only if either the actual position or the scrolled state has changed.
1 parent f44c5aa commit 01cc6f3

File tree

1 file changed

+33
-6
lines changed

1 file changed

+33
-6
lines changed

src/cdk/overlay/position/flexible-connected-position-strategy.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
115115
/** The last position to have been calculated as the best fit position. */
116116
private _lastPosition: ConnectedPosition | null;
117117

118+
/** The last calculated scroll visibility. Only tracked */
119+
private _lastScrollVisibility: ScrollingVisibility | null;
120+
118121
/** Subject that emits whenever the position changes. */
119122
private readonly _positionChanges = new Subject<ConnectedOverlayPositionChange>();
120123

@@ -710,18 +713,28 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
710713
this._addPanelClasses(position.panelClass);
711714
}
712715

713-
// Save the last connected position in case the position needs to be re-calculated.
714-
this._lastPosition = position;
715-
716716
// Notify that the position has been changed along with its change properties.
717717
// We only emit if we've got any subscriptions, because the scroll visibility
718718
// calculations can be somewhat expensive.
719719
if (this._positionChanges.observers.length) {
720-
const scrollableViewProperties = this._getScrollVisibility();
721-
const changeEvent = new ConnectedOverlayPositionChange(position, scrollableViewProperties);
722-
this._positionChanges.next(changeEvent);
720+
const scrollVisibility = this._getScrollVisibility();
721+
722+
// We're recalculating on scroll, but we only want to emit if anything
723+
// changed since downstream code might be hitting the `NgZone`.
724+
if (
725+
position !== this._lastPosition ||
726+
!this._lastScrollVisibility ||
727+
!compareScrollVisibility(this._lastScrollVisibility, scrollVisibility)
728+
) {
729+
const changeEvent = new ConnectedOverlayPositionChange(position, scrollVisibility);
730+
this._positionChanges.next(changeEvent);
731+
}
732+
733+
this._lastScrollVisibility = scrollVisibility;
723734
}
724735

736+
// Save the last connected position in case the position needs to be re-calculated.
737+
this._lastPosition = position;
725738
this._isInitialRender = false;
726739
}
727740

@@ -1289,6 +1302,20 @@ function getRoundedBoundingClientRect(clientRect: Dimensions): Dimensions {
12891302
};
12901303
}
12911304

1305+
/** Returns whether two `ScrollingVisibility` objects are identical. */
1306+
function compareScrollVisibility(a: ScrollingVisibility, b: ScrollingVisibility): boolean {
1307+
if (a === b) {
1308+
return true;
1309+
}
1310+
1311+
return (
1312+
a.isOriginClipped === b.isOriginClipped &&
1313+
a.isOriginOutsideView === b.isOriginOutsideView &&
1314+
a.isOverlayClipped === b.isOverlayClipped &&
1315+
a.isOverlayOutsideView === b.isOverlayOutsideView
1316+
);
1317+
}
1318+
12921319
export const STANDARD_DROPDOWN_BELOW_POSITIONS: ConnectedPosition[] = [
12931320
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top'},
12941321
{originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom'},

0 commit comments

Comments
 (0)