I'm trying to use CdkConnectedOverlay
to display an overlay when a button is clicked. It's mostly working, but the overlay is not re-positioning on scroll. I'm sure there's something small I'm missing, but I can't for the life of me figure it out.
I'm using Angular 7.2.8
and Angular CDK 7.3.3
Thought it might be related to missing cdk styles (similar to this), but I imported those; if the styles were missing, I would expect it not to display correctly in the first place. Mine just doesn't re-position on scroll.
Template:
<button
(click)="isOpen = !isOpen"
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
>Show</button>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayOpen]="isOpen"
>
Popover content
</ng-template>
Component:
@Component ( {
selector: 'app-popover',
templateUrl: './popover.component.html',
styleUrls: [ './popover.component.css' ],
changeDetection: ChangeDetectionStrategy.OnPush,
} )
export class PopoverComponent {
isOpen: boolean = false;
}
And a Plunkr showing the issue: https://stackblitz.com/edit/angular-7-popover
Update:
The scroll re-positioning issue only occurs when the popover is in an element that overflows its parent with overflow: auto
. If the page is overflowing, then it works fine. You can see this behavior with the following template
<div style="height: 300px; overflow-y: auto">
<!-- Scroll re-positioning does not work when scrolling in here -->
<div style="height: 100px"></div>
<app-popover>
Popover content
</app-popover>
<div style="height: 400px"></div>
</div>
<div style="height: 100px;"></div>
<!-- Scroll re-positioning works when scrolling here -->
<app-popover>
Popover content
</app-popover>
<div style="height: 1200px;"></div>
I've also updated the stackblitz to show this issue.
cdk documentation is not easy to understand sometimes and there are hidden gems all over it :)
here it states that;
Each strategy will typically inject ScrollDispatcher (from @angular/cdk/scrolling) to be notified of when scrolling takes place.
what i understand from this sentence is thatOverlay
repositions itself in response to events from ScrollDispatcher
. so, where do those scroll events come from?
unfortunately there isn't any information about this in docs. So i took a look in the codes and found this
/** Sets up the global scroll listeners. */
private _addGlobalListener() {
this._globalSubscription = this._ngZone.runOutsideAngular(() => {
return fromEvent(window.document, 'scroll').subscribe(() => this._scrolled.next());
});
}
which means ScrollDispatcher
listens for scroll events on window by default.
in your case above it responds to events when window is scrolled but not an inner container. this conforms to information we gathered so far.
here we can make the conclusion that Overlay
is not notified of scroll events that take place within the inner container and all we need to do is register inner container with ScrollDispatcher
at this point cdkScrollable directive comes into rescue and placing cdkScrollable
on the scrolling inner container solves the problem.
<div cdkScrollable style="height: 300px; overflow-y: auto">
here is a working demo