Prerequisite: cdk draggable elements inside a nested scrollable div
(see  the example)
How to reproduce:
Effect: item placeholder stays in wrong place and it's basically impossible to drag item anywhere outside the viewport.
<div style="height: 100vh; overflow-y: auto">
  <div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
    <div class="example-box" *ngFor="let movie of movies" cdkDrag>{{movie}}</div>
  </div>
</div>
                I've searched for this issue in the Angular components' official Github repository and I have found the following topics:
https://github.com/angular/components/issues/13588
https://github.com/angular/components/issues/16535
There are different solutions depending on the version that you use: Angular 9+ (works also with Angular 10) or Angular 8:
From version 9.1.0, the scrolling of the parent element is supported by setting the cdkScrollable directive to it.
So, for v9.1.0 and up, the following code should work:
<div style="height: 100vh; overflow-y: auto" cdkScrollable>
  <div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
    <div class="example-box" *ngFor="let movie of movies" cdkDrag>{{movie}}</div>
  </div>
</div>
Stackblitz demo:
https://stackblitz.com/edit/angular-swaqkk-yjiz7r (uses v10.0.1)
https://stackblitz.com/edit/angular-vszdat (uses v9.2.4)
From version 8.1.0, the scrolling was enabled, but only for the cdkDropList itself or the viewport (for performance reasons). So there are two solutions available:
overflow: scroll to the cdkDropList element:<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)" style="height: 100vh; overflow-y: auto">
  <div class="example-box" *ngFor="let movie of movies" cdkDrag>{{movie}} 
  </div>
</div>
Stackblitz demo:
https://stackblitz.com/edit/angular-avezy6
cdkDropList scrollable and there is a parent element that should scroll (like the situation in the question), I've adapted a solution found here (https://github.com/angular/components/issues/16677#issuecomment-562625427):
we can use a custom directive cdkDropListScrollContainer, that will be set on the cdkDrag elements. This directive will take as a Input the reference to the parent element that should scroll:<div class="example-container" style="height: 500px; overflow-y: auto" #scrollContainer>
  <div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
    <div
      class="example-box"
      *ngFor="let movie of movies"
      cdkDrag
      [cdkDropListScrollContainer]="scrollContainer">
      {{movie}}
    </div>
  </div>
</div>
The code for the directive is:
import { Directive, Input, ElementRef } from "@angular/core";
import { CdkDrag } from "@angular/cdk/drag-drop";
@Directive({
  selector: "[cdkDropListScrollContainer]"
})
export class CdkDropListScrollContainerDirective {
  @Input("cdkDropListScrollContainer") scrollContainer: HTMLElement;
  originalElement: ElementRef<HTMLElement>;
  constructor(cdkDrag: CdkDrag) {
    cdkDrag._dragRef.beforeStarted.subscribe(() => {
      const cdkDropList = cdkDrag.dropContainer;
      if (!this.originalElement) {
        this.originalElement = cdkDropList.element;
      }
      if (this.scrollContainer) {
        const element = this.scrollContainer;
        cdkDropList._dropListRef.element = element;
        cdkDropList.element = new ElementRef<HTMLElement>(element);
      } else {
        cdkDropList._dropListRef.element = cdkDropList.element.nativeElement;
        cdkDropList.element = this.originalElement;
      }
    });
  }
}
Stackblitz demo: https://stackblitz.com/edit/angular-jkuqhg
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With