4

So I am working in a sandbox to imitate the environment of the application I wish to implement this drag and drop in. Sorry to be so wordy but I really want to make sure I explain the problem properly as I have seen several older posts on this issue that either was not answered or answered the wrong question.

SHORT VERSION

  1. cdk dnd wouldnt autoscroll
  2. made my own autoscroll
  3. cdk dnd wont drop once scrolled

The trick is it appears that the cdk Drag and Drop autosroll functionality only works out of the box in relation to either the whole window size or, if it is a fixed height div with overflow-y: scroll (which is what I'm trying to do), then that needs to be at the level of the cdkDropList. Unfortunately due to the component architecture of my application, this is NOT currently an option.

I wrote my own autoscrolling functionality that scrolls the div based on the screen position of the mouse while an element is being dragged, I got that working but the big issue is that once the div starts scrolling you lose the ability to drop an item in the list.

Here is the HTML for my parent component (the div with the fixed height and overflow position)

<div #ScrollDiv style="height: 200px; width: 200px; overflow-y: scroll; padding: 5px;">
  <app-child (itemSelected)="itemSelection($event)"></app-child>
</div>

Here is the child component template with the cdk directives

   <div cdkDropList style="border: 2px solid purple;" (cdkDropListDropped)="drop($event)">
    <div cdkDrag [cdkDragData]="draggers" *ngFor="let item of draggers; let i = index"
            style="height: 30px; border: 2px solid blue; margin: 5px"
            (mousedown)="grabDrag()">
            {{item}}</div>   
    </div>

At this point, I can drag in my window, but auto-scroll doesn't work, so I added the auto scroll to the Type Script file of the parent component changing the scroll position on (mousemove). For the positions to start scrolling up/down the values are hardcoded bases on the page I'm working with.

import { Component, OnInit, ElementRef, ViewChild, AfterViewInit, ChangeDetectionStrategy } from '@angular/core';
import { Subscription, fromEvent } from 'rxjs';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit  {
  title = 'drag-sizing';
  itemSelected = false;
  isScrolling = false;
  smoothScroll;
  scrollSpeed = 2;
  @ViewChild('ScrollDiv', {static: true}) public myScrollContainer: ElementRef;

ngAfterViewInit() {
  fromEvent(this.myScrollContainer.nativeElement, 'mousemove').subscribe((event: MouseEvent) => {
    if (this.itemSelected) {
      if (event.clientY < 150) {
        this.isScrolling = true;
        if (!this.smoothScroll) {
          this.smoothScroll = setInterval(() => {
            this.scrollUp();
          }, 10);
        }
      } else if (event.clientY > 300) {
        this.isScrolling = true;
        if (!this.smoothScroll) {
          this.smoothScroll = setInterval(() => {
            this.scrollDown();
          }, 10);
        }
      } else {
        clearInterval(this.smoothScroll);
        this.smoothScroll = undefined;
        this.isScrolling = false;
      }
    } else {
      clearInterval(this.smoothScroll);
      this.smoothScroll = undefined;
      this.isScrolling = false;
    }
  });
}
  scrollInner() {
    this.myScrollContainer.nativeElement.scrollTop += 2;
  }
  itemSelection(event) {
    this.itemSelected = event;
    if (!this.itemSelected) {
      clearInterval(this.smoothScroll);
      this.smoothScroll = undefined;
    }
  } 
  scrollUp() {
    if (this.isScrolling === true) {
      this.myScrollContainer.nativeElement.scrollTop -= this.scrollSpeed;
    }
  }

  scrollDown() {
    if (this.isScrolling === true) {
      this.myScrollContainer.nativeElement.scrollTop += this.scrollSpeed;
    }
  }
}

and the child component TypeScript which emits that an item is being dragged and has the drop functionality

import { Component, OnInit, Input, ElementRef, ViewChild, AfterViewInit, Output, EventEmitter } from '@angular/core';
import { Subscription, fromEvent, BehaviorSubject, Subject } from 'rxjs';
import { CdkDragDrop, transferArrayItem, moveItemInArray } from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements  AfterViewInit {
@Output() itemSelected = new EventEmitter<boolean>();

  itemIsSelected = false;
  draggers = ['item 0', 'item 1', 'item 2'...]

  ngAfterViewInit(): void {
    fromEvent(document, 'mouseup').subscribe(() => {
      this.itemIsSelected = false;
      this.itemSelected.emit(this.itemIsSelected);
  });
  }
  grabDrag() {
    this.itemIsSelected = true;
    this.itemSelected.emit(this.itemIsSelected);
  }
  drop(event: CdkDragDrop<any>) {
    console.log(event);
    moveItemInArray(this.draggers, event.previousIndex, event.currentIndex);
  }
}

(there might be some stuff that I don't use left-over from some failed attempts)

ANYWAYS: I know this was long but the main behavior I am trying to overcome/ figure out is When I am dragging an item and scroll away from the small piece of the array in its initial view port, the cdk drag and drop no longer lets me drop the item into that position of the array... the item still appears to be DRAGGING but this leads me to believe that it no longer recognizes the DROP ZONE

Heres What it looks like

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62

4 Answers4

9

I think solution might be to put the cdkScrollable directive in a container which has components inside with CDK drag and drop.

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
Miroslav
  • 91
  • 1
  • 2
  • 1
    This worked for me - had to put it in the outer container. – Sean Chase Nov 03 '20 at 17:33
  • 1
    Still struggling to fix this, anybody has few codes? – Kalhan.Toress Mar 09 '21 at 19:07
  • 2
    I've added cdkScrollable directive to the div element that is scrollable (overflow) and in which my draggable list was. That solved the problem I was having with the drag & drop – Patrick Oct 11 '21 at 09:32
  • 1
    This absolutely worked for me but had to figure out where to place was issue as the project I was working was huge. So after removing all html to check which one was causing the issue.
    was on app.component.html where I had the from where my component was getting rendered. So added the cdkScrollable on
    and this helped me. And one more to checkout is where the overflow-y css was getting applied. Took me two days but finally got it resolved. Thanks a ton.
    – Saiteja Samala Apr 13 '22 at 13:37
4

cdkScrollable directive should be added to element that causes scrollbar to appear (causes overflow for viewport or element height). In my case it is direct parent of element that holds cdkDropList.

The important thing here is cdkScrollable on scrollable container. It can be far ancestor (in our case it's like that) and not direct parent.

To summarize, to enable autoscroll you need to import CdkScrollableModule and set cdkScrollable for scrollable container which should scroll on drag.

Try something like this:

<div cdkScrollable class="modal-content">
    <div cdkDropList class="data-content" (cdkDropListDropped)="drop($event)">
     <ng-container *ngFor="let column of columnList"
        [ngTemplateOutlet]="datarowcontent"
        [ngTemplateOutletContext]="{column}">
     </ng-container>
     <ng-template  let-column="column" #datarowcontent>
         <div cdkDrag class="data-row">
             <div cdkDragHandle class="edit-mode-icon">
                 <svg myDragIcon size="16"></svg>
             </div>
             <div class="content name">{{ column.name}}</div>
         </div>
     </ng-template>
    </div>
</div>
Tomas Zubrik
  • 347
  • 3
  • 11
0

Works for me like below:

<div cdkScrollable cdkDropList>
  <div *ngFor="let item of data" cdkDrag>
    <app-component [item]="item"></app-component>
  </div>
</div>

I used cdkScrollable with cdkDropList. Make sure to import CdkScrollableModule in module file.

Farman Ali
  • 97
  • 10
-1

Drop position of a draggable element located in a dialog box is calculated incorrectly if the page is scrolled down: DragRef._pointerMove uses method _getPointerPositionOnPage to determine the cursor position relative to viewport, which substracts the current scroll position (received from ViewportRuler) from the current cursor position. The problem is that in dialog windows (or Overlays with GlobalPositionStrategy to be exact) event coordinates are calculated without taking scroll offset into consideration. I forced top to be 0 (usually it is set to the -scrollOffset before opening dialog):

::ng-deep html.cdk-global-scrollblock {
    top: 0px !important;
  }
  • 2
    Remember that Stack Overflow isn't just intended to solve the immediate problem, but also to help future readers find solutions to similar problems, which requires understanding the underlying code. This is especially important for members of our community who are beginners, and not familiar with the syntax. Given that, **can you [edit] your answer to include an explanation of what you're doing** and why you believe it is the best approach? – Jeremy Caney Feb 14 '23 at 00:21