36

Is there a way to dynamically create drop zones? I'm having some troubles with ngFor and cdkDropList.

Here is my first list and draggable elements:

       <div class="subj-container" 
        cdkDropListOrientation="horizontal" 
        cdkDropList 
        #subjectList="cdkDropList"
        [cdkDropListData]="subjects"  
        [cdkDropListConnectedTo]="[lessonList]" 
        (cdkDropListDropped)="drop($event)"
        >
            <div class="subject" *ngFor="let subject of subjects" cdkDrag>
                {{subject.name}}
            </div>
        </div>

And here is my second list:

          <div class="conta" cdkDropList
                #lessonList="cdkDropList"
                [cdkDropListData]="appointment.lessons"
                [cdkDropListConnectedTo]="[subjectList]"
                (cdkDropListDropped)="drop($event)">
                    <div class="sub" cdkDrag *ngFor="let lesson of appointment.lessons">
                        {{lesson.name}}
                </div>
           </div>

Now, div with class 'conta' is inside of a *ngFor.

My problem is, I suppose, with my second list. If I drag an element from second list to list one, it works normally, but if I try to drag element from list one to any instance of list in second list, it can't recognize that the element is being dragged. Demo here:

problem demo

Am I doing something wrong here? The typescript part is working fine.

Thanks

Gonçalo Peres
  • 11,752
  • 3
  • 54
  • 83
sebamed
  • 1,047
  • 1
  • 9
  • 18
  • Does your second list work if you remove the styling that makes it multiline? The droplists can only be either horizontal or vertical. What you seem to have here is a grid, which won't work because there is inherent logic that relies on knowing whether to calculate the relative distances of droplist elements in x or y dimension. – Lightheaded Nov 11 '18 at 16:02
  • @Lightheaded - Yes, I actually thought of that, and removed all styles, but no. I found a solution. There was a problem with **cdkDropListConnectedTo**. It was connected to null, so I made my workaround. Check my answer bellow, and thanks! – sebamed Nov 11 '18 at 16:08
  • do you have working example of this? – Muhammad Daniyal Jul 15 '20 at 06:57

6 Answers6

37

After a full day of research, I found this pull request on Angular CDK repository on Github. Now, since I did not know how to integrate cdkDropListGroup into my example, I decited to create an array of IDs which will be added to [cdkDropListConnectedTo].

Each instance of my second list will have generated ID, and that ID will be added to array with suitable prefix (in my second list, on cdkDropList):

<div cdkDropList
      [attr.id]="addId(i, j)"
      [cdkDropListData]="appointment.lessons"
      [cdkDropListConnectedTo]="[subjectList]"
      (cdkDropListDropped)="drop($event)"
>

addId method:

addId(i, j) {
    this.LIST_IDS.push('cdk-drop-list-' + i + '' + j);
    return i + '' + j;
}

(cdk-drop-list- is an ID prefix. CDK places this prefix on every element with cdkDropList attribute)

So, my array will look like:

  • cdk-drop-list-00
  • cdk-drop-list-01
  • cdk-drop-list-02
  • etc.

Now, I pass that array to [cdkDropListConnectedTo] in my first list:

<div class="subj-container" 
    cdkDropListOrientation="horizontal"
    cdkDropList 
    #subjectList="cdkDropList"            
    [cdkDropListData]="subjects" 
    [cdkDropListConnectedTo]="LIST_IDS"
    (cdkDropListDropped)="drop($event)"
>

And it works flawlessly!

Hope this will help anybody with the same problem. Also, take a look at the pull request I mentioned, my solution is only a workaround, there is probably a better solution with cdkDropListGroup

sebamed
  • 1,047
  • 1
  • 9
  • 18
  • 1
    `cdkDropListGroup` is not a released feature yet. Look out for next releases. Meanwhile, I've been using the same approach – use a mapping by list IDs. There seems no better way to do it at the moment. Whenever your mentioned feature gets released, you can remove all that hacky messing with IDs :) – Lightheaded Nov 12 '18 at 05:52
  • 1
    cdkDropListGroup is released. Can I get this demo with cdkDropListGroup anywhere? – Jomy Joseph Nov 23 '18 at 15:58
  • 1
    I added a demo with cdkDropListGroup on StackBlitz at https://stackblitz.com/edit/angular-a4ftm7 – Maxxx Nov 27 '18 at 04:01
  • I also trying to achieve the same thing. Can anyone help me with it. Please find the link. https://stackoverflow.com/questions/59386696/how-to-get-mat-tree-node-element-from-drop-event-in-the-mat-tree-in-angular2 – Rahul Rai Dec 19 '19 at 06:34
  • The approach works fine for rows that can fits on a dialog box. However, the drag and drop cannot figure out the current index when there are too many rows that cannot fit on a visible area. If I select the last row, and try to move it to the top by vertical scroll bar, it failed. Any idea to fix the problem? – user3097695 Nov 19 '20 at 18:15
20

Source Link

Demo Link

For Dynamic Drag n Drop Lists, we can use ID instead of # Template variables

enter image description here

app.component.html

<div class="col-md-3" *ngFor="let week of weeks">
  <div class="drag-container">
    <div class="section-heading">Week {{week.id}}</div>

    <div cdkDropList id="{{week.id}}" [cdkDropListData]="week.weeklist"
      [cdkDropListConnectedTo]="connectedTo" class="item-list" (cdkDropListDropped)="drop($event)">
      <div class="item-box" *ngFor="let weekItem of week.weeklist" cdkDrag>Week {{week.id}} {{weekItem}}</div>
    </div>
  </div>
</div>

app.component.ts

import { Component } from '@angular/core';
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  weeks = [];
  connectedTo = [];


  constructor() {
    this.weeks = [
      {
        id: 'week-1',
        weeklist: [
          "item 1",
          "item 2",
          "item 3",
          "item 4",
          "item 5"
        ]
      }, {
        id: 'week-2',
        weeklist: [
          "item 1",
          "item 2",
          "item 3",
          "item 4",
          "item 5"
        ]
      }, {
        id: 'week-3',
        weeklist: [
          "item 1",
          "item 2",
          "item 3",
          "item 4",
          "item 5"
        ]
      }, {
        id: 'week-4',
        weeklist: [
          "item 1",
          "item 2",
          "item 3",
          "item 4",
          "item 5"
        ]
      },
    ];
    for (let week of this.weeks) {
      this.connectedTo.push(week.id);
    };
  }

  drop(event: CdkDragDrop<string[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      transferArrayItem(event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex);
    }
  }
}
Code Spy
  • 9,626
  • 4
  • 66
  • 46
18

With cdkDropListGroup you can now do:

<div cdkDropListGroup>

  <div cdkDropList
    [cdkDropListData]="data"
    (cdkDropListDropped)="drop($event)">
    <div class="row m-2">
        <div *ngFor="let i of data" cdkDrag>{{i}}</div>          
    </div>
  </div>

  <div class="subj-container" 
    cdkDropListOrientation="horizontal"
    cdkDropList 
    #subjectList="cdkDropList"            
    [cdkDropListData]="subjects" 
    (cdkDropListDropped)="drop($event)"> 
  </div>

</div>

No longer a need for cdkDropListConnectedTo in this case. See https://github.com/angular/material2/blob/master/src/cdk/drag-drop/drag-drop.md

yglodt
  • 13,807
  • 14
  • 91
  • 127
5

I had also to face to that problem. I tried the id approach but I didn't feel too much too confident while using. When I console.log in this addId() function, I can see the same id repeated several times. Instead of that, I tried to use the @ViewChildren decorator to have the cdkList components in real time, and it works very well for me.

In typescript

  cdkDropTrackLists: CdkDropList[];
  @ViewChildren(CdkDropList)
  set cdkDropLists(value: QueryList<CdkDropList>) {
    this.cdkDropTrackLists = value.toArray();
  }

In template

<div
        cdkDropList
        class="track-list"
        cdkDropListSortingDisabled
        [cdkDropListData]="paragraphIdentifiers"
        (cdkDropListDropped)="drop($event)"
        [cdkDropListConnectedTo]="cdkDropTrackLists">
</div>

I think I can improve it while cdkDropLists as a QueryList has a changes properties which an Observable.

Benoît Plâtre
  • 596
  • 1
  • 5
  • 4
2

I've built a stackblitz example using a dynamic group of lists and cdkDropListGroup. I'm memorizing the first list's name on the item in the list. By memorizing the first list I can track which objects have changed, which can be handy for building some cases. It also gives me the possibility to change the background color of the items that have moved, clearly showing what's changed.

James D
  • 1,975
  • 2
  • 17
  • 26
0

A bit late, but Helpful and worth sharing. I've created a kanban board using angular material version 10. Here is the link to the demo. https://stackblitz.com/edit/angular-material-kanban

The Mechanic
  • 2,301
  • 1
  • 26
  • 37