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