【问题标题】:Drag and drop custom static components using Angular Material CDK使用 Angular Material CDK 拖放自定义静态组件
【发布时间】:2020-11-04 06:52:07
【问题描述】:

参考 Angular CDK 拖放,我正在尝试创建一个带有左侧边栏和主要内容区域的简单仪表板。这两个区域都将包含独特的自定义组件,这些组件需要可拖动,并且可以在其包含的区域内重新排序并转移到另一个区域。

例如。边栏包含CompComp1,然后我可以在该区域内重新排列它们并将它们转移到主要内容区域。

据我了解,Angular Material CDK 拖放仅适用于列表。此外,列表中的项目必须具有相似的类型才能重新排序/转移。

有没有办法将CDKDragCDKDropList 用于静态项目而不是数组中的项目?我无法重新排序或将自定义组件转移到不同的下拉列表。

我创建了一个示例项目: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);
    }
  }
}

【问题讨论】:

    标签: angular typescript angular-material drag-and-drop


    【解决方案1】:

    Angular Material cdk 拖放功能在恢复元素的可见性并将其插入 其在 DOM 中的旧位置时的工作方式。

    它们保持位置很重要,因为它们依赖ngFor 指令,并且在 DOM 中移动元素可能会导致 NgFor仅在必要时进行智能差异和重新创建元素

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

    这是一个如何完成的示例:

    Stackblitz with manual dom manipulations

    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>
    

    Stackblitz with NgFor implementation

    【讨论】:

    • @zeeshan-siddiqui 有帮助吗?
    • 经过大量搜索,我发现这是唯一一个对我所面临的问题有很好解释的答案。如果您的组件不是由 *ngFor 动态生成的,并且您仍想使用 Angular 材质拖放 CDK,这是迄今为止我遇到的最佳方式。谢谢 - @yurzui
    猜你喜欢
    • 2019-05-05
    • 2020-09-30
    • 2023-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-24
    相关资源
    最近更新 更多