使用Angular Material CDK拖放自定义静态组件

4
关于 Angular CDK 的拖放功能,我正在尝试创建一个简单的仪表板,其中包括左侧边栏和主内容区。这两个区域都将包含独特的自定义组件,这些组件需要可拖动,并可以在其所包含的区域内重新排序并转移到另一个区域。
例如,边栏包含 Comp 和 Comp1,则可以在该区域内重新排序它们并将它们转移到主内容区。
据我所知,Angular Material CDK 拖放功能仅适用于列表。此外,列表中的项目必须是类似类型,以便它们可以被重新排序/转移。
有没有一种方法可以利用CDKDrag和CDKDropList来处理静态项而不是数组中的项?我无法重新排序或将自定义组件传输到不同的文件夹列表中。
我已经创建了一个示例项目:https://stackblitz.com/edit/ng-mat-dnd 演示: https://ng-mat-dnd.stackblitz.io/ app.component.html
<div class="example-container">
    <h2>Sidebar</h2>

    <div cdkDropList #sidebarList="cdkDropList" [cdkDropListData]="sidebar" cdkDropListConnectedTo="[mainList]"
        class="example-list" (cdkDropListDropped)="drop($event)">
        <div class="example-box" cdkDrag>
            <app-demo-comp-2 [btn]=2></app-demo-comp-2>
        </div>
        <div class="example-box" cdkDrag>
            <app-demo-comp [ddn]=2></app-demo-comp>
        </div>
        <div class="example-box" cdkDrag>
            <app-demo-comp-3 [txt]=3></app-demo-comp-3>
        </div>
    </div>
</div>

<div class="example-container">
    <h2>Main</h2>

    <div cdkDropList #mainList="cdkDropList" [cdkDropListData]="main" cdkDropListConnectedTo="[sidebarList]"
        class="example-list" (cdkDropListDropped)="drop($event)">
        <div class="example-box" cdkDrag>
            <app-demo-comp [ddn]=1></app-demo-comp>
        </div>
        <div class="example-box" cdkDrag>
            <app-demo-comp-2 [btn]=3></app-demo-comp-2>
        </div>
    </div>
</div>

app.component.ts

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

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

  @ViewChildren(CdkDrag) draggables: QueryList<CdkDrag>;

  constructor() { }

  ngOnInit() { }

  ngAfterViewInit() {
    console.log(this);
    this.sidebar = [this.draggables.toArray()[0], this.draggables.toArray()[1], this.draggables.toArray()[2]];
    console.log(this.sidebar);
    
    this.main = [this.draggables.toArray()[4], this.draggables.toArray()[3]];
    console.log(this.main);
  }

  drop(event: CdkDragDrop<any[]>) {
    console.log(event);
    if (event.previousContainer === event.container) {
      console.log('Same container');
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      console.log('Different containers');
      transferArrayItem(event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex);
    }
  }
}
1个回答

6

Angular Material的CDK拖放功能能够恢复元素的可见性,并将其插入到旧位置的DOM中,从而实现拖放功能。

重要的是保持位置不变,因为它们依赖于ngFor指令,在DOM中移动元素可能会使智能差分和重新创建元素的NgFor出现问题。

这意味着,如果你想让它在没有ngFor的情况下工作,你就必须自己移动html元素。

以下是如何实现的示例:

手动操作DOM的Stackblitz

drop(event: CdkDragDrop<any[]>) {
  const nodeToMove = event.item.element.nativeElement;
  const { previousContainer, container, previousIndex, currentIndex } = event;

  console.log(this.sidebar, this.main);

  if (previousContainer === container) {
    moveItemInArray(container.data, previousIndex, currentIndex);

    moveWithinContainer(
      container.element.nativeElement,
      previousIndex,
      currentIndex
    );
  } else {
    transferArrayItem(
      previousContainer.data,
      container.data,
      previousIndex,
      currentIndex
    );
    transferNodeToContainer(
      nodeToMove,
      container.element.nativeElement,
      currentIndex
    );

    Promise.resolve().then(() => {
      previousContainer.removeItem(event.item);
      event.item.dropContainer = container;
      event.item._dragRef._withDropContainer(container._dropListRef);
      container.addItem(event.item);
    });
  }
}

...

function moveWithinContainer(container, fromIndex, toIndex) {
  if (fromIndex === toIndex) {
    return;
  }

  const nodeToMove = container.children[fromIndex];
  const targetNode = container.children[toIndex];

  if (fromIndex < toIndex) {
    targetNode.parentNode.insertBefore(nodeToMove, targetNode.nextSibling);
  } else {
    targetNode.parentNode.insertBefore(nodeToMove, targetNode);
  }
}

function transferNodeToContainer(node, container, toIndex) {
  if (toIndex === container.children.length) {
    container.appendChild(node);
  } else {
    const targetItem = container.children[toIndex];
    targetItem.parentNode.insertBefore(node, targetItem);
  }
}

NgFor

你可能已经注意到上面的解决方案有些繁琐,并且在库版本不同的情况下可能会出现问题。

你可以使用ngFor指令来进行替代:

ts

sidebar = [0, 1, 2];
main = [0, 1];

html

 <div *ngFor="let item of sidebar" class="example-box" cdkDrag>
    <app-demo-comp-2 *ngIf="item === 0" [btn]=2></app-demo-comp-2>

    <app-demo-comp *ngIf="item === 1" [ddn]=2></app-demo-comp>

    <app-demo-comp-3 *ngIf="item === 2" [txt]=3></app-demo-comp-3>
 </div> 

 ...
 <div *ngFor="let item of main" class="example-box" cdkDrag>
    <app-demo-comp *ngIf="item === 0" [ddn]=1></app-demo-comp>

    <app-demo-comp-2 *ngIf="item === 1" [btn]=3></app-demo-comp-2>
 </div>

使用NgFor实现的Stackblitz示例


@zeeshan-siddiqui 有帮助吗? - yurzui
在经过大量搜索后,我发现这是唯一一个对我遇到的问题有很好解释的答案。如果您的组件不是通过*ngFor动态生成的,但仍想使用Angular Material拖放CDK,那么这是我迄今为止找到的最佳方法。谢谢 - @yurzui - Manas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接