Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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>
like image 997
user10552124 Avatar asked Sep 02 '19 10:09

user10552124


1 Answers

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:

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

like image 137
andreivictor Avatar answered Sep 29 '22 16:09

andreivictor