4

I've been wrecking my brain on this problem for quite some time now. What I want to achieve is to have two connected drag and drop lists with constant length. Meaning that if I move an element from one list to another, an item gets pushed to the other list. This is of course trivial to achieve during the cdkDropListDropped event, but I want it to happen as soon as the item is dragged over the list.

Most of my attempts have involved using the cdkDropListEntered event to either:

  1. Attempt to simply move the item in the data array:
public enter(list: number, event: CdkDragEnter<User[]>) {
  if (list === 0) {
    let data = this.schedule.responsible.pop();
    this.schedule.queue.unshift(data);
  } else {
    let data = this.schedule.queue.shift();
    this.schedule.responsible.push(data);
  }
}

This resulted in errors of the type:

core.js:6185 ERROR DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node

  1. Tried use the CdkDropList addItem(), removeItem(), getSortedItems(). This lead to similar problems.

  2. Tried to move the DOM-elements themselves with Renderer2 (and leaving data untouched)

Is there any way to achieve what I want?

This magnificent paint drawing helps explain what I want to achieve.

Prateek Gupta
  • 2,422
  • 2
  • 16
  • 30
Kim Ulvik
  • 61
  • 1
  • 8

1 Answers1

2

Okay, I've figured it out after attempting two solutions. The first one involved adding placeholder boxes to both lists which were only visible if they had content. Their content would be the content of the box pushed into that list. Meanwhile the original box was given the display: none style. This achieved mostly the behaviour I wanted but had several visual issues, due to mismatch between the internal model of the draggable and the DOM.

What eventually ended up working was to abandon the concept of having two lists in the first place. Then sorting resolved itself naturally. However, styling has to be done a little bit differently as every draggable needs to be a direct descendant of the list.

Attaching both the code and a working Stackblitz example:

app.component.ts

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


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

    public lists: {list1: string[], list2: string[]};
    public fullList: string[];
    public numList1: number;

    constructor() {}

    ngOnInit() {
        this.lists = {
            list1: ['one', 'two'],
            list2: ['three', 'four']
        };
        this.fullList = this.lists.list1.concat(this.lists.list2);
        this.numList1 = this.lists.list1.length;
    }

    public drop(event: CdkDragDrop<string[]>) {
        moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    }
}

app.component.html

<div class="list-container">
  <div cdkDropList
      [cdkDropListAutoScrollDisabled]="true"
      [cdkDropListData]="fullList"
      cdkDropListLockAxis="y"
      (cdkDropListDropped)="drop($event)">
    <ng-container *ngFor="let item of fullList; let index = index;">
      <h2 *ngIf="index === 0">List 1</h2>
      <h2 *ngIf="index === numList1">List 2</h2>
      <div cdkDrag class="drop-box">{{item}}</div>
    </ng-container>
  </div>
</div>

Stackblitz: https://stackblitz.com/edit/angular-ivy-s7zfye

Kim Ulvik
  • 61
  • 1
  • 8