Angular Material's Drag and Drop between lists if there's no data model

6.5k Views Asked by At

Update:

I've created a stackblitz to reproduce the problem.


Consider the following static template I have:

<div class="websiteleftColumn" cdkDropList #left="cdkDropList"
  [cdkDropListConnectedTo]="[right]">
    <div class="panelWithMuchContent" cdkDrag> much HTML </div>
    <div class="panelWithMuchContent" cdkDrag> much HTML </div>
    <div class="panelWithMuchContent" cdkDrag> much HTML </div>
</div>

<div class="websiteRightColumn" cdkDropList #right="cdkDropList"
  [cdkDropListConnectedTo]="[left]">
    <div class="panelWithMuchContent" cdkDrag> much HTML </div>
    <div class="panelWithMuchContent" cdkDrag> much HTML </div>
    <div class="panelWithMuchContent" cdkDrag> much HTML </div>
</div>

I would expect to be able to freely reorder the panels in both columns and also between the two. Instead, I'm able to drag the panels, but as soon as I drop one, nothing happens, it moves back to its original state.

A guess it's because there is no data model behind. In the examples, the draggable divs are rendered on the page by *ngFor from an array. And there's also a drop(event: CdkDragDrop<string[]>) component method bound to them that updates the data model every time a drop happens.

But my problem is that I don't just have so simple list elements that I could put in some array, but entire parts of the website with much HTML code inside that I want to drag.

How could I create a data model for it? (if it's really that Angular's missing)

2

There are 2 best solutions below

0
On BEST ANSWER

See this answer for the reason why it was not working and here's also an example how drag and drop is properly implemented using ng-templates and a data model:

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

@Component({
  selector: 'hello',
  template: `

    <div class="row" cdkDropListGroup>

      <div class="col-6" cdkDropList [cdkDropListData]="list1"
        (cdkDropListDropped)="panelDropped($event)">

        <div cdkDrag *ngFor="let p of list1">
          <ng-container *ngTemplateOutlet="this[p+'Panel']">
          </ng-container>
        </div>

      </div>

      <div class="col-6" cdkDropList [cdkDropListData]="list2"
        (cdkDropListDropped)="panelDropped($event)">

        <div cdkDrag *ngFor="let p of list2">
          <ng-container *ngTemplateOutlet="this[p+'Panel']">
          </ng-container>
        </div>

      </div>

    </div>

    <ng-template #aPanel><div class="card"></div></ng-template>
    <ng-template #bPanel><div class="card"></div></ng-template>
    <ng-template #cPanel><div class="card"></div></ng-template>
    <ng-template #dPanel><div class="card"></div></ng-template>
    <ng-template #ePanel><div class="card"></div></ng-template>
    <ng-template #fPanel><div class="card"></div></ng-template>

  `,
  styles: [`

    .col-6:nth-child(1) { background-color: plum; }
    .col-6:nth-child(2) { background-color: peru; }
    .card { background-color: blue; height: 10em; margin: 0.5em; }

  `]
})
export class HelloComponent {
  @Input() name: string;

  @ViewChild('aPanel') aPanel: TemplateRef<any>;
  @ViewChild('bPanel') bPanel: TemplateRef<any>;
  @ViewChild('cPanel') cPanel: TemplateRef<any>;
  @ViewChild('dPanel') dPanel: TemplateRef<any>;
  @ViewChild('ePanel') ePanel: TemplateRef<any>;
  @ViewChild('fPanel') fPanel: TemplateRef<any>;

  list1: Array<string> = ['a', 'b', 'c'];
  list2: Array<string> = ['d', 'e', 'f'];

  panelDropped(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);
    }
  }

}
3
On

First to connect all lists you can use the following (I am not sure if this could solve your dragging reset issue):

<div cdkDropListGroup>
  <!-- All lists in here will be connected. -->
  <div cdkDropList *ngFor="let list of lists"></div>
</div>

It should be displayed on the angular components site.

What you are currently creating is static values, and if you have dynamic values that can be generate, then using *ngFor would very likely be preferable.

The content within the droplists will remain the same as that is displayed. So using a *ngFor you create on single template of how you want an element in the list to be displayed. You can therefor have different "templates" in each dropList. Having both droplists connected/linked, will make it possible for you to transfer the content no matter what, but the displayed content would likely remain the same as the template.

I cannot say for sure how you can handle dynamic data that is undefined, but you can probably categorize the type of data you want to handle and create a template for such. Then take advantage of *ngIF and display the data as desired if it is of a certain type.