@@ -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+
12921319export 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