angulardrag-and-dropangular-materialangulardraganddroplists

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


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)


Solution

  • 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);
        }
      }
    
    }