Skip to content

Commit 67956e0

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. (cherry picked from commit 01cc6f3)
1 parent d7c062a commit 67956e0

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)