Angular CDK - issue with scrolling and dragging element inside nested scrollable div
Asked Answered
M

1

13

Prerequisite: cdk draggable elements inside a nested scrollable div (see the example)

How to reproduce:

  1. Start dragging an item.
  2. Scroll the page
  3. Drag item a bit more when not scrolling

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>
Mcmahon answered 2/9, 2019 at 10:11 Comment(3)
Have you tried the virtual scroll cdk for the scrollable div?Arrange
I'm having the same issue. Did you ever find a solution for this?Tympanist
Does someone have a solution?Leifleifer
H
10

I've searched for this issue in the Angular components' official Github repository and I have found the following topics:

There are different solutions depending on the version that you use: Angular 9+ (works also with Angular 10) or Angular 8:

Angular 9+ (works also Angular 10)

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)


Angular 8

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:

  1. We can set the fixed height and the 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

  1. If we can't make the 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

Hunfredo answered 4/11, 2020 at 19:21 Comment(1)
On using the directive method I am getting an error as "Cannot assign to 'element' because it is a read-only property." for line "cdkDropList._dropListRef.element = element" and "cdkDropList._dropListRef.element = cdkDropList.element.nativeElement"Clydesdale

© 2022 - 2024 — McMap. All rights reserved.