Angular/CDK Tree数据更新重渲染后如何实现持续实时拖拽
拖拽树形组件实时UI更新问题
更新: 提供了Stackblitz示例
说明: 我的目标是实现拖拽过程中UI实时更新。技术方案可完全不同(当前方案可能行不通),只要不是维护不佳的小众包,用原生HTML+CSS实现也可
当前方案问题:修改底层数据并首次重渲染后,cdkDragMoved回调不再触发,但我希望拖拽能持续进行,直到用户释放鼠标(拖拽结束),期间可反复检测并更新数据(拖拽过程中已完成更新)。推测重渲染时CdkDrag被销毁重建?
问题: 如何实现在拖拽过程中持续实时更新UI,直到用户释放鼠标?
我基于节点层级数据构建分类树,通过ng-template递归渲染单个分类及其子树结构。使用CDK实现拖拽功能,在cdkDragMoved回调中,将拖拽位置与所有分类边界框比对,匹配到非自身分类时更新底层数据。数据更新会触发ngOnInit中设置的订阅逻辑,更新本地数据并触发重渲染。
// ...imports type CategoryId = string; @Component({ selector: 'app-category-tree-a', template: ` <!-- ---------------------------------------------------------------------- --> <!-- RECURSIVE SUB-TREE TEMPLATE --> <!-- ---------------------------------------------------------------------- --> <ng-template #subtree let-node="node"> <div cdkDrag [cdkDragData]="node.categoryModel.id" (cdkDragStarted)="dragStarted($event)" (cdkDragMoved)="dragMoved($event)" (cdkDragReleased)="dragReleased($event)" > <div class="flex flex-col h-10 select-none" [ngStyle]="{ backgroundColor: node.categoryModel.color, marginLeft: node.depth * 16 + 'px' }" > {{ node.categoryModel.name }} </div> <ng-container *ngFor="let child of node.children"> <ng-container *ngTemplateOutlet="subtree; context: { node: child }"> </ng-container> </ng-container> </div> </ng-template> <!-- ---------------------------------------------------------------------- --> <!-- ROOT SUB-TREES --> <!-- ---------------------------------------------------------------------- --> <div *ngFor="let rootNode of rootNodes"> <ng-container *ngTemplateOutlet="subtree; context: { node: rootNode }"> </ng-container> </div> `, }) export class CategoryTreeAComponent implements OnInit, OnDestroy { constructor(public appService: AppService, public dataService: DataService) {} subscriptions: Array<Subscription> = []; /** * Last categoriesById received from DataService. * Used to create rootNodes and to look up by id during drag. */ categoriesById: CategoriesById = new Map(); /** * Last rootNodes created from last received categoriesById * Used to render the hierarchy and to find related nodes during drag. */ rootNodes: CategoryNode[] = []; isChangePending = false; // all category elements identified by having the CdkDrag directive @ViewChildren(CdkDrag<CategoryId>) dragDirectives!: QueryList< CdkDrag<CategoryId> >; // ---------------------------------------------------------------------- // Lifecycle // ---------------------------------------------------------------------- ngOnInit(): void { this.subscriptions.push( this.dataService.categoriesById$.subscribe((categoriesById) => { console.log(`ngOnInit: categoriesById => next `); this.categoriesById = categoriesById; this.rootNodes = DataHelper.createNodesFromCategoryById(categoriesById); this.isChangePending = false; }) ); } ngOnDestroy(): void { this.subscriptions.forEach((s) => s.unsubscribe()); } // ---------------------------------------------------------------------- // Drag & Drop // ---------------------------------------------------------------------- dragStarted(event: CdkDragStart<CategoryId>) { console.log(`dragStarted: ${event.source.data}`); } async dragMoved(event: CdkDragMove<CategoryId>) { const pointerPos = JSON.stringify(event.pointerPosition); console.log(`dragMoved: $mouse=${pointerPos} id:${event.source.data}`); if (this.isChangePending) { console.log(`...isChangePending. abort.`); return; } // position of dragged element const dragNative = event.source.element.nativeElement; const dragChildElement = dragNative.querySelector('div:first-child'); const dragChildRect = dragChildElement!.getBoundingClientRect(); const dragMidY = dragChildRect.top + dragChildRect.height / 2; // drag directive of div of sub-tree of "matched" category or null // matched means mid-y of dragged category element is inside // (category is first child of group div with drag directive) // (using drag directive because it has the data attached) // (is also null if the match is with itself) let matchDrag: CdkDrag<CategoryId> | null = null; for (const testDrag of this.dragDirectives) { const testNative = testDrag.element.nativeElement; const testChild = testNative.querySelector('div:first-child'); const testRect = testChild!.getBoundingClientRect(); const testId = testDrag.data; const testNode = DataHelper.findNodeById(this.rootNodes, testId); if (!testNode) break; const testName = testNode.categoryModel.name; const isInside = dragMidY >= testRect.top && dragMidY <= testRect.bottom; const isSelf = testNative === dragNative; // console.log( // `...${testName.padEnd(14)} ${isSelf ? '*' : ' '} rect=${JSON.stringify( // testRect // )}` // ); if (isInside && !isSelf) { matchDrag = testDrag; break; } } if (matchDrag !== null) { const sourceDrag = event.source; const targetDrag = matchDrag; const sourceId = sourceDrag.data; const targetId = targetDrag.data; const sourceNode = DataHelper.findNodeById(this.rootNodes, sourceId); const targetNode = DataHelper.findNodeById(this.rootNodes, targetId); if (sourceNode === null) { console.log(`sourceNode is null. abort.`); return; } if (targetNode === null) { console.log(`targetNode is null. abort.`); return; } const sourceName = sourceNode.categoryModel.name; const targetName = targetNode.categoryModel.name; const sourceSiblings = sourceNode.parent?.children ?? this.rootNodes; const targetSiblings = targetNode.parent?.children ?? this.rootNodes; const sourceIndex = sourceSiblings.indexOf(sourceNode); const targetIndex = targetSiblings.indexOf(targetNode); console.log( `...MATCH - from: ${sourceName} (${sourceIndex}) to:${targetName} (${targetIndex})` ); sourceSiblings.splice(sourceIndex, 1); targetSiblings.splice(targetIndex, 0, sourceNode); this.isChangePending = true; if (sourceSiblings === targetSiblings) { // reordering siblings of same parent const updatedCategories = DataHelper.updateSiblingCategories(sourceSiblings); await this.dataService.updateCategories(updatedCategories); } else { // moving between parents const updatedCategories = [ ...DataHelper.updateSiblingCategories(sourceSiblings), ...DataHelper.updateSiblingCategories(targetSiblings), ]; await this.dataService.updateCategories(updatedCategories); } } } dragReleased(event: CdkDragRelease<CategoryModel>) { console.log(`dragReleased: ${event.source.data}`); // event.source.reset(); } }
内容的提问来源于stack exchange,提问作者phlebotinum
相关产品推荐
相关产品推荐

