components icon indicating copy to clipboard operation
components copied to clipboard

[Drag and drop] problems when changing height after start

Open JurajMlich opened this issue 6 years ago • 21 comments
trafficstars

What is the expected behavior?

Drag and drop working :)

What is the current behavior?

If I change the height of elements (that are draggable) for example by hiding some elements after I start dragging, the drag and drop mechanism thinks the items are still the original height (and therefore rendering placeholder at wrong place and behaving incorrectly).

What are the steps to reproduce?

https://stackblitz.com/edit/angular-material2-issue-ggdtyr?file=app%2Fapp.component.css

(moving between columns does not work for some unknown reason but that is not important)

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

newest ones

Is there anything else we should know?

If you point me in the right direction, I can provide a fix.

JurajMlich avatar Jan 02 '19 17:01 JurajMlich

@JurajMlich A temporal hack fix to that issue, on the cdkDragStarted event i put this, it is not a nice solution but meanwhile it is fixed.

  dragStarted(event: {source: CdkDrag}) {
    setTimeout(() => {
      event.source._dragRef.dropContainer['_cachePositions']();
    }, 200);
  }

sergiocosus avatar Jan 21 '19 18:01 sergiocosus

@JurajMlich I tried to attempt the same thing. I just hide content inside of draggable elements when an element starts dragging.
.cdk-drop-list-dragging { mat-card { mat-card-content { display: none; } } }

@sergiocosus I am getting an error when I tried your solution.

Property 'dropContainer' does not exist on type 'DragRef<CdkDrag<any>>'. Did you mean '_dropContainer'?ts(2551)
drag-ref.d.ts(137, 13): '_dropContainer' is declared here.

vorachirag avatar Oct 23 '19 10:10 vorachirag

@vorachirag it worked before the update. Now this works:

dragStarted(event: {source: CdkDrag}) {
  setTimeout(() => {
    const dropContainer = event.source._dragRef['_dropContainer'];

    if (dropContainer) {
      dropContainer['_cacheOwnPosition']();
      dropContainer['_cacheItemPositions']();
    }
  });
}

thabalija avatar Oct 23 '19 13:10 thabalija

@thabalija Thanks. It works.

vorachirag avatar Oct 23 '19 14:10 vorachirag

Bumping to a P2 since it seems like an issue that people keep bumping into, based on the number of duplicate issues.

crisbeto avatar Jul 12 '20 09:07 crisbeto

There are a variety of problems going on with this issue and those recently closed.

One of which is that connected drop list client rects are not being cached after an action occurs that could affect the rendered layout (e.g. transferring an item between lists with no fixed size).

This problem can be alleviated with something like the following:

// drop-list-ref.ts
 enter(item: DragRef, pointerX: number, pointerY: number, index?: number): void {
   this.start();

   // ...add item to this drop list's draggables 

   // Note that the positions were already cached when we called `start` above,
   // but we need to refresh them since the amount of items has changed and also parent rects.
   this._cacheItemPositions();
   this._cacheParentPositions();

   // NEW - remeasure connected drop list client rects
   this._siblings.forEach(sibling => {
       sibling._clientRect = sibling.element.getBoundingClientRect();
   });

   this.entered.next({item, container: this, currentIndex: this.getItemIndex(item)});
 }

(though obviously not using private variables directly and perhaps setting up a public method to re-cache the drop list client rect)

Example of problem (StackBlitz): GIF2

With above solution (uncomment lines 108-110): GIF3


The other main issue I see, which is going to require a good deal of refactoring to fix, is that this library's functionality completely starts to fall apart once you start adding layout changing CSS styles to the classes added by dynamic host bindings (e.g. .cdk-drop-list-dragging). These classes being added/removed require change detection to run, which is an awful dependency for a general purpose library like this, and change detection is generally running after rects have been cached, causing all these measurements to [potentially] be invalidated and never refreshed. I talked about this a little more here.

Achilles1515 avatar Jul 21 '20 20:07 Achilles1515

@crisbeto any news on how to manage this case simply? I'm trying to drag and drop an element on an area that is collapsed and uncollapsed on hovering but it's really tedious.

GitHubish avatar Feb 22 '21 10:02 GitHubish

I have a similar issue with my elements. They have a dynamic height but it should not change while dragging. However the placeholder height seems to be wrong as it will overlap the remaining elements (it's correctly visible but height seems to be smaller than real height resulting in the overlap)

digaus avatar Mar 17 '21 00:03 digaus

@crisbeto any news on how to manage this case simply? I'm trying to drag and drop an element on an area that is collapsed and uncollapsed on hovering but it's really tedious.

I have literally the same problem. Is there any fix or workaround for this?

Edit: I've tried @Achilles1515's solution, it will work, but I need to trigger it at a specific condition. How can I do that?

YSFKBDY avatar Mar 26 '21 12:03 YSFKBDY

Thank you @crisbeto for referring my issue to this one. For the record here is a Stackblitz repro.

BenRacicot avatar Mar 15 '22 18:03 BenRacicot

I have some glitches even when the height does not change, but when the element underneath need some rerendering (same height still). The list from that point just freezes, even sort preview does not work then.

Azbesciak avatar Feb 14 '23 04:02 Azbesciak

I had a task to increase the area of all containers when the move started. I had a task to increase the area of all containers when the move started. I had to override the start and enter methods for this in the prototype. I understand how bad this is, but a quick solution. Helped in this answer Achilles1515.

import {
  CdkDragDrop,
  DragDrop, DragRef,
  DropListRef,
  moveItemInArray,
  transferArrayItem
} from '@angular/cdk/drag-drop';

export class MyComponentWithStartCotainer  { 
  ...
}

DropListRef.prototype.start = function(force?: boolean) {
  const styles = coerceElement(this.element).style;
  this.beforeStarted.next();
  this._isDragging = true;
  // this._initialScrollSnap = styles.msScrollSnapType || (styles as any).scrollSnapType || '';
  (styles as any).scrollSnapType = styles.msScrollSnapType = 'none';
  this._cacheItems();
  this._siblings.forEach(sibling => sibling._startReceiving(this));
  // this._viewportScrollSubscription.unsubscribe();
  // this._listenToScrollEvents();

  if (!force) {
    setTimeout(() => {
      const item = this._draggables[0];
      this.exit(item);
      this.start(true);
    }, 0);
  }
}

  DropListRef.prototype.enter = function(item: DragRef, pointerX: number, pointerY: number, index?: number): void {
    (this.start as (force: boolean) => void)(true);

    let newIndex: number;

    if (index == null) {
      newIndex = this.sortingDisabled ? this._draggables.indexOf(item) : -1;

      if (newIndex === -1) {
        newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY);
      }
    } else {
      newIndex = index;
    }

    const activeDraggables = this._activeDraggables;
    const currentIndex = activeDraggables.indexOf(item);
    const placeholder = item.getPlaceholderElement();
    let newPositionReference: DragRef | undefined = activeDraggables[newIndex];
    if (newPositionReference === item) {
      newPositionReference = activeDraggables[newIndex + 1];
    }
    if (currentIndex > -1) {
      activeDraggables.splice(currentIndex, 1);
    }
    if (newPositionReference && !this._dragDropRegistry.isDragging(newPositionReference)) {
      const element = newPositionReference.getRootElement();
      element.parentElement!.insertBefore(placeholder, element);
      activeDraggables.splice(newIndex, 0, item);
    } else if (this._shouldEnterAsFirstChild(pointerX, pointerY)) {
      const reference = activeDraggables[0].getRootElement();
      reference.parentNode!.insertBefore(placeholder, reference);
      activeDraggables.unshift(item);
    } else {
      this.element.appendChild(placeholder);
      activeDraggables.push(item);
    }
    placeholder.style.transform = '';
    this._cacheItemPositions();
    this._cacheParentPositions();

    this.entered.next({item, container: this, currentIndex: this.getItemIndex(item)});
  }

The contents of the start and enter methods may differ. I took for version 9 from the source on GitHub

The key here is the repeated call of methods for caching elements is setTimeout. And when we call the start method again (in the enter method), we no longer do repeated caching.

oleksandr-kupenko avatar Feb 24 '23 11:02 oleksandr-kupenko

This has changed a bit in newer versions since the last answer but the issue remains. I've found that simply calling _cacheParentPositions() on DropListRef whenever the placeholders are resized/repositioned is a sufficient workaround.

bezo97 avatar Mar 07 '24 13:03 bezo97

Hello!

Is there anyone who can help with a case: all my elements are expandable panels. I need collapse all elements before dragging and ensure that dragging element works correctly;

https://stackblitz.com/edit/angular-csru2j-f21c6s?file=src%2Fapp%2Fexpansion-overview-example.ts

AnnaKozlova avatar Mar 11 '24 13:03 AnnaKozlova

Hello

We currently are experiencing the following issue: When we add an element, in our case an drop zone on drag start and the height of the row changes, then we can not drop the item. Somehow the drop list will not be recognized unless you move the item over the row in a circle for a second or two. Yet its not always the same behavior sometimes it works like it should and sometimes it doesn't work at all.

Does anyone have an solution for this issue?

F.Y. I tried the ones in this thread already but none of them worked for us.

raphael-bison avatar Apr 08 '24 05:04 raphael-bison

I have a similar issue with a Angular 17 component. It's a set of columns that overflow off screen (horizontally). When drag starts I adjust their width style so flex makes them fit on the page. After doing these adjustments, the dragging element doesn't seem to recognise that i t has been dragged into a list (no drag entered event).

Weirdly once I drag over to the first column (the one that hasn't really changed position) the whole system works correctly. Some sort of public method to re-calculate drop list boundaries would be appreciated, I could call that after dragging starts.

I've tried a few things like calling _cacheParentPostions, or triggering a drag entered event on the first element, or dispatching a resize event to the window (AI suggested). Nothing really seems to help.

Aidan-Chey avatar Apr 10 '24 00:04 Aidan-Chey

Hello,

I'm stuck at a similar problem (at least i think so)

I have 3 Lists with interchangable items. A List is only shown initialy if it has at least one item. Otherwise height and visiblility are changed so it's not visible:

.cdk-drop-list {
  margin: 0;
  //background: radial-gradient(85.63% 310.97% at 19.17% 24.48%, rgba(121, 128, 127, 0.9) 0.08%, rgba(84, 89, 89, 0.9) 100%);
  background: #00000033;
  border-radius: 8px;
  box-shadow: inset 0 0 4px 1px #00000033;
  height: auto;
  min-height: 45px;
}
//this is just my last try, similar stylings have the same result
.cdk-drop-list:not(.cdk-drop-list-receiving):not(.show) {
  visibility: hidden;
  min-height: unset;
}

Now if i start dragging ('show') is set true and as a result the list(-container) is shown. But I'm not able to drag any items into it. The interesting part!!: This is unless i scroll just a tiny bit. I tried to simulate this with code in typescript but it didnt work.

If dont use my styling with min-height, or any other attempt to change the height dynamically it works, but of course this is a no go from the visual/UX standpoint.

I wasnt able to get it to work even with the workarounds like suggested here by @thabalija

Are there any ideas what i could try to fix this?

Edit: I was able to reproduce our code and problem in stackblitz: https://stackblitz.com/edit/stackblitz-starters-iwvwyg?file=src%2Fapp%2Fapp-module.component.html

Some interesting observations: It is possible to drag into the last list, as long as there are items in the first list ('todo')! or i add more items to the second list, so i have to scroll into the last list.

Any advice would be highly appreciated :)

IBot18 avatar May 21 '24 14:05 IBot18

I stumbled upon this issue when investigating this very bug in our app where the container sizes change as you drag large items around, making it very hard to drop items at desired locations. As a workaround I eventually had to resort to code violence =/

During dragging operation, I call _cacheParentPositions() on the DropListRef every 100 ms.

antur84 avatar May 23 '24 09:05 antur84

I stumbled upon this issue when investigating this very bug in our app where the container sizes change as you drag large items around, making it very hard to drop items at desired locations. As a workaround I eventually had to resort to code violence =/

During dragging operation, I call _cacheParentPositions() on the DropListRef every 100 ms.

Could you provide a full code snippet for me, maybe in my stackblitz reproduction?

Edit: I made it work in my example, have to try it in production code. But as you said "violance" is the right term. Wish there was a solution in the framework for this...

IBot18 avatar May 23 '24 13:05 IBot18

I stumbled upon this issue when investigating this very bug in our app where the container sizes change as you drag large items around, making it very hard to drop items at desired locations. As a workaround I eventually had to resort to code violence =/ During dragging operation, I call _cacheParentPositions() on the DropListRef every 100 ms.

Could you provide a full code snippet for me, maybe in my stackblitz reproduction?

Edit: I made it work in my example, have to try it in production code. But as you said "violance" is the right term. Wish there was a solution in the framework for this...

Aha I missed your edit, yeah glad it's working for you. Yes the library code obvisouly had a performance problem they wanted to solve with caching, but now we have a problem with caching instead 🍰

antur84 avatar May 23 '24 14:05 antur84

Got it to work in production code as well. Had to use multiple surpresses for eslint. Whoever will do the review in my team, will scratch his head on this one for sure...

IBot18 avatar May 23 '24 15:05 IBot18

What worked for me is changeDetectorRef.detectChanges(). Since the Table Height got changed, because of the drop-zone item which got displayed when *ngIf="this.isDragging".

dragStarted(event: CdkDragStart<Email>) { this.dragItem = event.source.data; this.isDragging = true; this.changeDetectorRef.detectChanges(); }

raphael-bison avatar Jun 06 '24 08:06 raphael-bison

collapse

image image

I have resolved the issue as follows:

  1. Update your state to collapse all items
  2. Update dragItems
  3. Call detectChanges
  4. Set display: none for collapsed content

huy4429dev avatar Jun 13 '24 03:06 huy4429dev

confirm that .detectChanges() helps after collapsing/hiding html elements, then drag placeholder works correctly.

thegtzz avatar Jul 22 '24 11:07 thegtzz

EN_US

If you got here trying to make a paginated kanban or dynamic kanban and are having problems with the cdkDropList breaking, here is my TS with the definitive solution to many problems.

In my case when I drag an item down, I get more information from cards that were not rendered, after I change my "listsPcp" (that's what I call my card list) I reset the column cache and remove the css that was causing the cdkDropList ordering to break.

For some reason after dropping a dragged item, the 'transform' property is added and thus breaks the cdkDropList

Call the resetCache function after you change your list of cards (or components)!

PT_BR

Se você chegou aqui tentando fazer um kanban paginado ou kanban dinâmico e está tendo problemas com a quebra do cdkDropList, aqui está meu TS com a solução definitiva para muitos problemas.

No meu caso, quando arrasto um item para baixo, obtenho mais informações de cards que não foram renderizados, depois que altero meu "listsPcp" (é assim que chamo minha lista de cards), redefino o cache de colunas e removo o css que estava causando a quebra da ordenação do cdkDropList.

Por algum motivo, depois de soltar um item arrastado, a propriedade 'transform' é adicionada e, portanto, quebra o cdkDropList.

CODE / CÓDIGO

"@angular/cdk": "^19.0.1", "@angular/cdk-experimental": "^19.0.1",

HTML

<div cdkDropListGroup cdk-scrollable class="card-internal d-flex">
    <div *ngFor="let column of columns; let idx = index" class="card card-status">

      <div class="card-header">
         {{ column.name }}
      </div>

      <div
        cdkDropList
        *ngIf="!column.loading"
        style="width: 100%; height: 800px"
        class="card-body list-group"
        [ngStyle]="{'background-color': '#f6f6f6'}"
        [cdkDropListData]="listsPcp[idx]"
        [cdkDropListAutoScrollDisabled]="false"
        (cdkDropListDropped)="cdkDropListDropped($event, idx)"
        (scroll)="scrolledIndexChange(idx, 0, true)"
        [cdkDropListAutoScrollStep]="5"
      >

        @for (item of listsPcp[idx]; track item; let item_idx = $index;) {
          <div
            [id]="'column_' + idx"
            style="transform: none !important;"
            cdkDrag
            [cdkDragData]="lastCardDragged"
            (cdkDragStarted)="onDragStarted($event, item, idx, item_idx)"
          >
            <ng-container *ngTemplateOutlet="itemTemplate; context: { $implicit: item }"></ng-container>
          </div>
        }

      </div>
    </div>
  </div>

TS

  @ViewChildren(CdkDropList) dropLists: QueryList<CdkDropList>;
  @ViewChildren(CdkDrag) card: QueryList<CdkDrag>;

  private resetCache(event: any, columnIdx: number): void {
    setTimeout(() => {

      const cards: CdkDrag[] = this.card.filter((card: any) => card.element.nativeElement.id === 'column_' + columnIdx);
      console.log(cards);
      cards.forEach((card: CdkDrag) => card.element.nativeElement.style.transform = '');

      if (!cards || cards.length === 0) return;

      if (!this.dropLists.get(columnIdx)) return;

      this.dropLists.get(columnIdx)._dropListRef.withItems(cards.map((drag) => drag._dragRef));

      this.dropLists.get(columnIdx)._dropListRef.connectedTo(this.dropLists
        .filter((dropList2) => this.dropLists.get(columnIdx).id !== dropList2.id)
        .map((droplist2) => droplist2._dropListRef)
      );

      this.dropLists.get(columnIdx)._dropListRef['_sortStrategy']._cacheItemPositions();
      this.dropLists.get(columnIdx)._dropListRef['_cacheParentPositions']();

      this.cdr.detectChanges();

      if (!event || !event.source || !event.source._dragRef) return;

      const dropContainer: DropListRef = event.source._dragRef._dropContainer;
      if (dropContainer) dropContainer['_cacheParentPositions']();

    }, 200);
  }

Firekepr avatar Nov 29 '24 13:11 Firekepr

I implemented the last solution https://github.com/angular/components/issues/14703#issuecomment-2507785680 But it isn't working 100%. Are there any new solutions?

Sh3ldoris avatar Apr 02 '25 14:04 Sh3ldoris

I implemented the last solution #14703 (comment) But it isn't working 100%. Are there any new solutions?

The column is still broken even with the code I passed?

Firekepr avatar Apr 02 '25 15:04 Firekepr

@Firekepr I am unable to get to work with my variation of MatTab. I have a simple reproduction on StackBlitz here: https://stackblitz.com/edit/8hxqhzyg-rdoxrwhp?file=package.json. Can you spare some time to take a look?

Manish-Pradhan-FP avatar Apr 29 '25 19:04 Manish-Pradhan-FP

@Firekepr I am unable to get to work with my variation of MatTab. I have a simple reproduction on StackBlitz here: https://stackblitz.com/edit/8hxqhzyg-rdoxrwhp?file=package.json. Can you spare some time to take a look?

Ok, I must confess, you have a good problem, I spent about 3 hours trying to solve this problem, applying everything I know and I couldn't reach a solid solution, sometimes it worked and sometimes it didn't. I know that cdkDragAndDrop has problems with cdkDropList that don't exist in the DOM at the time a card was dragged, but with already rendered mat-groups the problem is probably something else.

I messed up the code a lot in order to try to help, I'll post the code below the image. From my analysis, probably the CDK is not detecting the cdkDropList, but even I reset the CDK cache it doesn't work, so I don't really know how to help

As you can see, here I take a print at the exact moment when it works fine, but this is unstable, it doesn't work 100% of the time.

Image

Sorry I can't help much.

As i said this is unstable And it's very messy

HTML

  <mat-tab-group
    preserveContent="true"
    fpFormDesignerMatTabGroup
    class="example-drag-tabs"
    [(selectedIndex)]="selectedTabIndex"
    animationDuration="0"
    cdkDropListGroup
  >
    @for (tab of tabs; let index = $index; track $index) {
      <mat-tab cdk-scrollable>
        <ng-template mat-tab-label>
          <span>{{tab}}</span>
        </ng-template>

        <ul
          [id]="'list_'+ index"
          cdkDropList
          [cdkDropListData]="cards[index]"
          (mouseenter)="enter(index)"
          (cdkDropListDropped)="drop($event)"
          (cdkDropListEntered)="enforceDragToSelf()"
        >
          @for (item of cards[index]; track item; let item_idx = $index;) {

            <li [id]="'column_' + index" cdkDrag (cdkDragStarted)="start(index)" [cdkDragData]="item">
              {{ item }} Drag Me to another Tab, Original: {{ index }}
            </li>

            <ng-template cdkDragPreview></ng-template>
          }
        </ul>
      </mat-tab>
    }
  </mat-tab-group>

TS

@ViewChildren(CdkDropList) dropLists: QueryList<CdkDropList>;
  @ViewChildren(CdkDrag) card: QueryList<CdkDrag>;

  isDrag = false;
  intstart;

  protected tabs = ['One', 'Two', 'Three', 'Four', 'Five'];
  protected selectedTabIndex = 0;

  cards = [[ 1-1, 2-1, 3-1], [ 1-2, 2-2, 3-2], [ 1-3, 2-3, 3-3]]

  drop(event: CdkDragDrop<number[], any>) {
    this.isDrag = false
    const prevActive = this.tabs[this.selectedTabIndex];
    moveItemInArray(this.tabs, event.previousIndex, event.currentIndex);
    this.selectedTabIndex = this.tabs.indexOf(prevActive);
  }
  
  start(idx) {
    this.intstart = idx;

    this.cdr.detectChanges();

    this.dropLists.get(idx)._dropListRef.connectedTo(this.dropLists
      .filter((dropList2) => this.dropLists.get(idx).id !== dropList2.id)
      .map((droplist2) => droplist2._dropListRef)
    );

    try {
      this.dropLists.get(idx)._dropListRef['_sortStrategy']._cacheItemPositions();
      this.dropLists.get(idx)._dropListRef['_cacheParentPositions']();
    } catch (error) {

    }

    this.cdr.detectChanges();

    this.isDrag = true;
  }

  enter(idx: number) {

    if (!this.isDrag) return;

    try {
      const cards: CdkDrag[] = this.card.filter((card: any) => card.element.nativeElement.id === 'column_' + idx);
      this.dropLists.get(idx)._dropListRef.withItems(cards.map((drag) => drag._dragRef));

      this.dropLists.get(idx)._dropListRef.connectedTo(this.dropLists
        .filter((dropList2) => this.dropLists.get(idx).id !== dropList2.id)
        .map((droplist2) => droplist2._dropListRef)
      );

      this.dropLists.get(idx)._dropListRef['_sortStrategy']._cacheItemPositions();
      this.dropLists.get(idx)._dropListRef['_cacheParentPositions']();
    } catch (error) {

    }

    this.cdr.detectChanges();
  }

  public enforceDragToSelf(): void {
    asapScheduler.schedule(() => {
      this.dropLists.forEach((dropList) => {
        let dropListRef = dropList._dropListRef;
        let siblings = this.dropLists.map((dl) => dl?._dropListRef);
        siblings = siblings.sort( (a,b) => a.data.id.localeCompare(b.data.id))
        dropListRef._getSiblingContainerFromPosition = (item, x, y) =>
          siblings.find((sibling) => sibling._canReceive(item, x, y));
      });
    });
  }

Firekepr avatar Apr 30 '25 12:04 Firekepr

@Firekepr I am unable to get to work with my variation of MatTab. I have a simple reproduction on StackBlitz here: https://stackblitz.com/edit/8hxqhzyg-rdoxrwhp?file=package.json. Can you spare some time to take a look?

Ok, I must confess, you have a good problem, I spent about 3 hours trying to solve this problem, applying everything I know and I couldn't reach a solid solution, sometimes it worked and sometimes it didn't. I know that cdkDragAndDrop has problems with cdkDropList that don't exist in the DOM at the time a card was dragged, but with already rendered mat-groups the problem is probably something else.

I messed up the code a lot in order to try to help, I'll post the code below the image. From my analysis, probably the CDK is not detecting the cdkDropList, but even I reset the CDK cache it doesn't work, so I don't really know how to help

As you can see, here I take a print at the exact moment when it works fine, but this is unstable, it doesn't work 100% of the time.

Image

Sorry I can't help much.

As i said this is unstable And it's very messy

HTML

  <mat-tab-group
    preserveContent="true"
    fpFormDesignerMatTabGroup
    class="example-drag-tabs"
    [(selectedIndex)]="selectedTabIndex"
    animationDuration="0"
    cdkDropListGroup
  >
    @for (tab of tabs; let index = $index; track $index) {
      <mat-tab cdk-scrollable>
        <ng-template mat-tab-label>
          <span>{{tab}}</span>
        </ng-template>

        <ul
          [id]="'list_'+ index"
          cdkDropList
          [cdkDropListData]="cards[index]"
          (mouseenter)="enter(index)"
          (cdkDropListDropped)="drop($event)"
          (cdkDropListEntered)="enforceDragToSelf()"
        >
          @for (item of cards[index]; track item; let item_idx = $index;) {

            <li [id]="'column_' + index" cdkDrag (cdkDragStarted)="start(index)" [cdkDragData]="item">
              {{ item }} Drag Me to another Tab, Original: {{ index }}
            </li>

            <ng-template cdkDragPreview></ng-template>
          }
        </ul>
      </mat-tab>
    }
  </mat-tab-group>

TS

@ViewChildren(CdkDropList) dropLists: QueryList<CdkDropList>;
  @ViewChildren(CdkDrag) card: QueryList<CdkDrag>;

  isDrag = false;
  intstart;

  protected tabs = ['One', 'Two', 'Three', 'Four', 'Five'];
  protected selectedTabIndex = 0;

  cards = [[ 1-1, 2-1, 3-1], [ 1-2, 2-2, 3-2], [ 1-3, 2-3, 3-3]]

  drop(event: CdkDragDrop<number[], any>) {
    this.isDrag = false
    const prevActive = this.tabs[this.selectedTabIndex];
    moveItemInArray(this.tabs, event.previousIndex, event.currentIndex);
    this.selectedTabIndex = this.tabs.indexOf(prevActive);
  }
  
  start(idx) {
    this.intstart = idx;

    this.cdr.detectChanges();

    this.dropLists.get(idx)._dropListRef.connectedTo(this.dropLists
      .filter((dropList2) => this.dropLists.get(idx).id !== dropList2.id)
      .map((droplist2) => droplist2._dropListRef)
    );

    try {
      this.dropLists.get(idx)._dropListRef['_sortStrategy']._cacheItemPositions();
      this.dropLists.get(idx)._dropListRef['_cacheParentPositions']();
    } catch (error) {

    }

    this.cdr.detectChanges();

    this.isDrag = true;
  }

  enter(idx: number) {

    if (!this.isDrag) return;

    try {
      const cards: CdkDrag[] = this.card.filter((card: any) => card.element.nativeElement.id === 'column_' + idx);
      this.dropLists.get(idx)._dropListRef.withItems(cards.map((drag) => drag._dragRef));

      this.dropLists.get(idx)._dropListRef.connectedTo(this.dropLists
        .filter((dropList2) => this.dropLists.get(idx).id !== dropList2.id)
        .map((droplist2) => droplist2._dropListRef)
      );

      this.dropLists.get(idx)._dropListRef['_sortStrategy']._cacheItemPositions();
      this.dropLists.get(idx)._dropListRef['_cacheParentPositions']();
    } catch (error) {

    }

    this.cdr.detectChanges();
  }

  public enforceDragToSelf(): void {
    asapScheduler.schedule(() => {
      this.dropLists.forEach((dropList) => {
        let dropListRef = dropList._dropListRef;
        let siblings = this.dropLists.map((dl) => dl?._dropListRef);
        siblings = siblings.sort( (a,b) => a.data.id.localeCompare(b.data.id))
        dropListRef._getSiblingContainerFromPosition = (item, x, y) =>
          siblings.find((sibling) => sibling._canReceive(item, x, y));
      });
    });
  }

Thanks @Firekepr. Yes I too have spent many hours trying to make this work. In my case too it works some times and not others. I have found if I scroll on the new Tab, the droplists starts listening and works as expected.

Manish-Pradhan-FP avatar Apr 30 '25 13:04 Manish-Pradhan-FP