You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建可复用Angular组件,实现cdkDropList的外部引用?

重构Angular CDK拖拽列表为可复用组件:动态模板引用与跨实例关联方案

核心思路

将重复的拖拽列表封装为独立组件,通过输入属性传递配置、暴露组件内部的cdkDropList实例,由父组件统一管理所有列表的关联关系,彻底消除重复代码。


步骤1:创建可复用的Kanban列表组件

组件类(kanban-list.component.ts)

import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
import { CdkDropList, CdkDragDrop } from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-kanban-list',
  templateUrl: './kanban-list.component.html',
  styleUrls: ['./kanban-list.component.css']
})
export class KanbanListComponent {
  // 输入属性:列表标题、数据源、关联的拖拽列表集合
  @Input() listTitle!: string;
  @Input() listItems!: any[];
  @Input() connectedLists!: CdkDropList[];
  
  // 输出事件:拖拽完成后向父组件传递事件对象
  @Output() dropped = new EventEmitter<CdkDragDrop<any[]>>();

  // 获取当前组件内部的cdkDropList实例,暴露给父组件调用
  @ViewChild('dropList') dropList!: CdkDropList;

  // 保留原有的时间格式化逻辑
  foregoneTime(time: number): string {
    const now = Date.now() / 1000;
    const diff = Math.floor(now - time);
    if (diff < 60) return `${diff}s`;
    if (diff < 3600) return `${Math.floor(diff/60)}m`;
    if (diff < 86400) return `${Math.floor(diff/3600)}h`;
    return `${Math.floor(diff/86400)}d`;
  }

  // 转发拖拽事件到父组件处理
  drop(event: CdkDragDrop<any[]>) {
    this.dropped.emit(event);
  }
}

组件模板(kanban-list.component.html)

<div class="example-container">
  <h2>{{ listTitle }}</h2>

  <div class="example-list">
    <div
      cdkDropList
      #dropList="cdkDropList"
      [cdkDropListData]="listItems"
      [cdkDropListConnectedTo]="connectedLists"
      (cdkDropListDropped)="drop($event)"
    >
      <div class="example-box" *ngFor="let item of listItems" cdkDrag>
        <div>
          <strong>
            <a target="_blank" [href]="item.url">{{item.title}}</a>
          </strong>&nbsp;
          <a
            *ngIf="item.hostname"
            class="hn"
            target="_blank"
            [href]="'https://news.ycombinator.com/from?site='+item.hostname"
          >
            ({{item.hostname}})
          </a>
        </div>
        <div class="secondRow">
          <span>by {{item.by}}&nbsp;</span>
          <span>Points: {{item.score}}&nbsp;</span>
          <span>{{foregoneTime(item.time)}} ago&nbsp;</span>
        </div>
        <div class="thirdRow">
          <a target="_blank" [href]="item.url">Visit</a>&nbsp;
          <a
            class="hn"
            target="_blank"
            [href]="'https://news.ycombinator.com/item?id='+item.id"
            >{{item.descendants}} comments</a
          >
        </div>
      </div>
    </div>
  </div>
</div>

步骤2:在父组件中使用可复用组件并管理关联关系

父组件类(drag-drop.component.ts)

import { Component, ViewChildren, QueryList, AfterViewInit } from '@angular/core';
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
import { KanbanListComponent } from './kanban-list/kanban-list.component';

@Component({
  selector: 'app-drag-drop',
  templateUrl: './drag-drop.component.html',
  styleUrls: ['./drag-drop.component.css']
})
export class DragDropComponent implements AfterViewInit {
  // 初始化各列表的数据源(替换为你的实际业务数据)
  newsItems = [];
  topItems = [];
  readLaterItems = [];
  watchItems = [];

  // 获取所有可复用列表组件的实例集合
  @ViewChildren(KanbanListComponent) kanbanLists!: QueryList<KanbanListComponent>;

  ngAfterViewInit() {
    // 收集所有列表的cdkDropList实例,传递给每个列表作为关联目标
    const allDropLists = this.kanbanLists.map(list => list.dropList);
    this.kanbanLists.forEach(list => {
      list.connectedLists = allDropLists;
    });
  }

  // 统一处理所有拖拽事件
  drop(event: CdkDragDrop<any[]>) {
    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
      );
    }
  }
}

父组件模板(drag-drop.component.html)

<app-kanban-list
  [listTitle]="'News 📰'"
  [listItems]="newsItems"
  (dropped)="drop($event)"
></app-kanban-list>

<app-kanban-list
  [listTitle]="'Top 🚀'"
  [listItems]="topItems"
  (dropped)="drop($event)"
></app-kanban-list>

<app-kanban-list
  [listTitle]="'Read Later 📚'"
  [listItems]="readLaterItems"
  (dropped)="drop($event)"
></app-kanban-list>

<app-kanban-list
  [listTitle]="'Watch 🎥'"
  [listItems]="watchItems"
  (dropped)="drop($event)"
></app-kanban-list>

关键说明

  1. 动态模板引用管理:子组件通过@ViewChild获取自身的cdkDropList实例,父组件用@ViewChildren批量收集所有子组件实例,无需手动定义多个不同的模板引用变量。
  2. 跨实例关联:在ngAfterViewInit生命周期钩子中,将所有列表的dropList实例数组传递给每个子组件的connectedLists输入属性,实现所有列表之间的拖拽互通。
  3. 逻辑复用:拖拽事件统一在父组件处理,子组件仅负责渲染和事件转发,确保业务逻辑集中、代码可维护性提升。

内容的提问来源于stack exchange,提问作者user11092881

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 13:25:31