Reposition CdkConnectedOverlay on scroll
Asked Answered
G

1

10

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.

Gurglet answered 25/6, 2019 at 0:6 Comment(0)
A
22

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

Anatolic answered 27/6, 2019 at 13:4 Comment(4)
Ah, that makes sense now! I also came across this issue on Github about the same time you posted this answer, which is saying essentially the same thing. github.com/angular/components/issues/6157Gurglet
For those who need it, in order to have cdkScrollable work, you will need to import ScrollingModule. import {ScrollingModule} from '@angular/cdk/scrolling'; ..... imports: [ ....., ScrollingModule]Lucier
Is there no solution to listen to any scroll events without having to add cdkScrollable directive? It could be an expensive operation though.Hellenist
why would you need that?Anatolic

© 2022 - 2024 — McMap. All rights reserved.