Angular Material: Not able to drop on a dynamically created droplist

2.8k Views Asked by At

For some reason (read: Cannot create many components), I have to create sections on my app which will be able to act as drop elements. (cannot use *ngIf)

I created DropListRef like -

constructor(private dragDropService: DragDrop) {} 

const dropListRef = this.dragDropService
      .createDropList(dropZone)
      .withItems([]);

And I am trying to detect whether an element has been dropped as -

dropListRef.dropped.subscribe(event => {
      console.log("dropped: ");
});

However, I never find the console output.

Link to sample code - https://stackblitz.com/edit/angular-r3spyc

Link to demo video - https://drive.google.com/file/d/1b6RUuOk9r7s9vFd3NlhgR9ot1hHP1Oeb/view

Cannot create many components

  1. The way I have written in not atypical with the examples shown on the Angular official site, however, the same docs advertise an ability to createDropList() here given a native-element. If you inspect it further - the code creates a DropListRef and adds an element to the DOM, however, it does not respond to events such as dropped, entered. It feels that I am missing some important steps like registering it somewhere or connecting with the other DropList which then Angular Material can recognize or identify it.
  2. The reason I cannot create a single component (for example - a Dropbox component) because that would be oversimplifying the problem at hand. There are too many complexities associated with the problem to be encapsulated in a single component like the Dropbox component (or many). I might just end up creating 100+ ng-templates (or different components) if I go the route of creating a template for each use-case.
1

There are 1 best solutions below

2
On

The way you code is not a typical way to code for Angular. I looked up the Angular official site and made a little tweak for you.

I've created a drop-box component with drop-box.component.html:

<div>{{text}}</div>

drop-box.component.ts:

import { Component, Input } from "@angular/core";

@Component({
  selector: "drop-box",
  templateUrl: "./drop-box.component.html",
  styleUrls: ["./drop-box.component.scss"]
})
export class DropboxComponent {
  @Input() text = "";
  constructor() {}
}

For your app.component.ts:

import { Component } from "@angular/core";
import {
  CdkDragDrop,
  CdkDrag,
  DragDropRegistry,
  DragDrop,
  DragRef,
  moveItemInArray,
  transferArrayItem
} from "@angular/cdk/drag-drop";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.scss"]
})
export class AppComponent {
  title = "angular-draggable";
  previousIndex = 0;

  todo = ["a", "b", "c", "d", "e"];
  list = ["default item"];
  isEnabled = false;

  constructor(private dragDropService: DragDrop) {}

  onDragStart(event, i) {
    console.log("drag started!", event);
    console.log('event.previousIndex',event.previousIndex);
    console.log('i',i);
    this.previousIndex = i;
  }

  createDroplist() {
    this.isEnabled = !this.isEnabled;
  }

  drop(event: CdkDragDrop<string[]>){
    if(this.isEnabled)
    {
      transferArrayItem(event.previousContainer.data,
                        event.container.data,
                        this.previousIndex,
                        event.currentIndex);
    }
  }
}

Note that the "Click me to create droplist" button and the createDroplist method is probably not needed anymore. But I put it there to enable/disable the dragging in case you still need it.

And the app.component.html:

<div cdkDropListGroup>
  <div class="example-container">
    <h2>To do</h2>

    <div style="position: relative;">
      <ng-container *ngFor="let item of todo; let i = index">
        <div cdkDropList #todoList="cdkDropList" class="example-list" [cdkDropListData]="todo" [cdkDropListConnectedTo]="[doneList]">
          <div class="example-box stupid">{{item}}</div>
          <div class="example-box" (cdkDragStarted)="onDragStart($event, i)" cdkDrag>{{item}}</div>
        </div>
      </ng-container>
    </div>
  </div>


  <button (click)="createDroplist()">
    Click me to create droplist
  </button>
  <div id='box' cdkDropList
    #doneList="cdkDropList"
    [cdkDropListData]="list"
    [cdkDropListConnectedTo]="[todoList]"
    (cdkDropListDropped)="drop($event)">
    <div *ngFor="let item of list">
      <drop-box [text]="item"></drop-box>
    </div>
  </div>
</div>

You can check out my example here. Again, to drag from the todo list, you now need to first click the "Click me to create droplist" button, then drop.

Happy coding!