components
components copied to clipboard
DragDrop Autoscroll feature not working with Material Table
Relates to https://github.com/angular/components/pull/16382
Reproduction
https://stackblitz.com/edit/angular-qczaca
Steps to reproduce:
- Try dragging a row to the edge of its container.
- You can see that Material Table cannot be autoscrolled while a simple container can be... (like the div in the Stackblitz example).
Expected Behavior
I can use autoscroll feature with Material Table as well.
Actual Behavior
Autoscroll does not work with Material Table.
Environment
Angular, CDK/Material:
"@angular/animations": "~8.2.0",
"@angular/cdk": "~8.1.2",
"@angular/common": "~8.2.0",
"@angular/compiler": "~8.2.0",
"@angular/core": "~8.2.0",
"@angular/forms": "~8.2.0",
"@angular/material": "^8.1.2",
"@angular/platform-browser": "~8.2.0",
"@angular/platform-browser-dynamic": "~8.2.0",
"@angular/router": "~8.2.0",
"hammerjs": "^2.0.8"
"@angular-devkit/build-angular": "~0.802.0",
"@angular/cli": "~8.2.0",
"@angular/compiler-cli": "~8.2.0",
"@angular/language-service": "~8.2.0"
@woteska
The problem is the scroll container not being used in the process, instead another element is assumed to be the scroll container.
The cdkDropList will use the element it is defined on as the scroll node, without being able to change it (cdkDrag for example can allow changing the root container)
With your first example
<div cdkDropList class="simple-list">
<div *ngFor="let item of items" cdkDrag>drag-me-please-{{item}}</div>
</div>
The scroll container is the first DIV which is also the cdkDropList element.
So the drag process uses the proper elements.
In the second example
<div class="example-container mat-elevation-z8">
<table mat-table [dataSource]="dataSource" cdkDropList>
</table>
</div>
The scroll container is actually the first DIV and the cdkDropList is the TABLE element.
The drag list will use the TABLE element as the scroll container, however its not.
The TABLE element will render to full height which is 2456px while the DIV will be 200px so actually you never reach the last 5% at the end, except when you fully scrolled to the end which is redundant anyway....
I'v already asked for this feature ages ago because it has other issue it blocks... but the team decided it's not a good thing.
https://github.com/angular/components/issues/14148
BTW, also worth mentioning that the entire placeholder positioning will not work on virtual lists because it's not built for that... the positioning will go all wrong because of it not taking into account dragging of items that are out of view.
FYI, as a workaround, you can use a "plugin like" directive.
import { Directive, Input, ElementRef } from '@angular/core';
import { CdkDropList } from '@angular/cdk/drag-drop';
@Directive({
selector: '[cdkDropList][actualContainer]',
})
export class CdkDropListAcualContainer {
@Input('actualContainer') actualContainer: string;
originalElement: ElementRef<HTMLElement>;
constructor(cdkDropList: CdkDropList) {
cdkDropList._dropListRef.beforeStarted.subscribe( () => {
if (!this.originalElement) {
this.originalElement = cdkDropList.element;
}
if (this.actualContainer) {
const element = this.originalElement.nativeElement.closest(this.actualContainer) as HTMLElement;
cdkDropList.element = new ElementRef<HTMLElement>(element);
cdkDropList._dropListRef.element = element;
} else {
cdkDropList.element = this.originalElement;
cdkDropList._dropListRef.element = cdkDropList.element.nativeElement;
}
});
}
}
The directive itself might need some polishing for more advanced cases, tear down etc...
<div class="example-container mat-elevation-z8">
<table mat-table [dataSource]="dataSource" cdkDropList actualContainer="div.example-container">
</table>
</div>
What happens here is that actualContainer="div.example-container" activates the workaround directive, which search's UP the dom tree for the first element matching the selector provided and define it as the root element for the drag list directive.
Here is a stackblitz example you can use
I can see that there is an issue in above example. first time you want do drag row in the table beyond the container view (with the scroll), row is not moving correctly. It looks like it's moved only between rows of initial view of the table ( rows that are visible when row is dragged first time). Also param of drop event returns wrong indexes. Second drag and drops works correctly. any Ideas how to solve this ?
I can see that there is an issue in above example. first time you want do drag row in the table beyond the container view (with the scroll), row is not moving correctly. It looks like it's moved only between rows of initial view of the table ( rows that are visible when row is dragged first time). Also param of drop event returns wrong indexes. Second drag and drops works correctly. any Ideas how to solve this ?
In the current version, beforeStarted on the cdkDropList declares the element before calling beforeStarted; however, beforeStarted on the cdkDrag calls on time to avoid the issue you mentioned. Adjusting @shlomiassaf's solution, the following worked for me:
Add actualContainer="div.example-container" to the element with cdkDrag instead of cdkDropList and adjust the directive as following:
import { Directive, Input, ElementRef, AfterViewInit } from '@angular/core';
import { CdkDrag } from '@angular/cdk/drag-drop';
@Directive({
selector: '[cdkDrag][actualContainer]',
})
export class CdkDropListActualContainerDirective {
@Input('actualContainer') actualContainer: string;
originalElement: ElementRef<HTMLElement>;
constructor(cdkDrag: CdkDrag) {
cdkDrag._dragRef.beforeStarted.subscribe( () => {
var cdkDropList = cdkDrag.dropContainer;
if (!this.originalElement) {
this.originalElement = cdkDropList.element;
}
if ( this.actualContainer ) {
const element = this.originalElement.nativeElement.closest(this.actualContainer) as HTMLElement;
cdkDropList._dropListRef.element = element;
cdkDropList.element = new ElementRef<HTMLElement>(element);
} else {
cdkDropList._dropListRef.element = cdkDropList.element.nativeElement;
cdkDropList.element = this.originalElement;
}
});
}
}
And like indicated in this issue, it is not just tables. It happens anytime an outside element is scrollable, which is quite often in web apps. You can see it clearly not work with a slightly modified Angular Example from the CDK page by adding a scrollable outside element:
https://stackblitz.com/edit/angular-swaqkk
But it does seem to work with horizontal scroll: https://stackblitz.com/edit/angular-2rzs2i
This solution(for Angular 9+) helped me for my case. Angular CDK - issue with scrolling and dragging element inside nested scrollable div
@jraadt,
The solution is not well documented, but it works like a charm. Add cdkScrollable to that div with outside classname. Here is the link, tell me if it helped:
https://stackblitz.com/edit/angular-swaqkk-jdsxoo?file=src%2Fapp%2Fcdk-drag-drop-sorting-example.html
Is there a way to make it scroll faster?
Yeah it needs to scroll faster, otherwise while works, it is practically useless.
EDIT: the solution from here
- https://stackoverflow.com/questions/62671963/angular-cdk-drag-drop-adjust-autoscroll-speed-when-dragging
SUMMARY: use cdkDropListAutoScrollStep, for example [cdkDropListAutoScrollStep]=20.