Anonymous
Drag-and-Drop
Post
by Anonymous » 15 Feb 2025, 12:07
Ich arbeite an einer dynamischen verschachtelten Baumstruktur mit angularem CDK -Drag & Drop. Die Drag-and-Drop-Funktion funktioniert auf der ersten Ebene, fällt jedoch fehl, wenn versucht wird, Elemente tiefer in der Hierarchie fallen zu lassen. Das CDKDropListDropped -Ereignis wird auf tieferen Ebenen nicht ausgelöst. (nach LV12)
Demo: https://nested-- angular.netlify.app
Codequelle: https: // GitHub. com/phamhung075/verschachtelter Baum
Wie kann ich sicherstellen, dass Drag & drop auf allen verschachtelten Ebenen funktioniert?
Vorschläge zum Beheben oder Debuggen dieses Problems?
Code: Select all
// tree.component.ts
import {
CdkDrag,
CdkDragDrop,
CdkDropList,
DragDropModule,
moveItemInArray,
} from '@angular/cdk/drag-drop';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import { TreeNode } from '@shared/interfaces/tree-node.model';
import { TreeService } from '@shared/services/tree/tree.service';
@Component({
selector: 'app-tree',
standalone: true,
imports: [CommonModule, FormsModule, DragDropModule],
templateUrl: './branch-display.component.html',
styleUrls: ['./branch-display.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TreeComponent implements OnInit {
@Input() node!: TreeNode;
@Input() isLastChild = false;
@Input() isRoot = true;
@Input() dropListIds: string[] = [];
@Output() onDelete = new EventEmitter();
@Output() registerDropList = new EventEmitter();
dropListId = `drop-list-${Math.random().toString(36).substring(2)}`;
private hasBeenInitialized = false;
constructor(
private treeService: TreeService,
private changeDetectionRef: ChangeDetectorRef
) {}
ngOnInit() {
if (!this.hasBeenInitialized) {
this.registerDropList.emit(this.dropListId);
console.log('Tree Component Initialized:', {
nodeId: this.node.id,
value: this.node.value,
children: this.node.children.length,
});
this.hasBeenInitialized = true;
}
}
canDrop = (drag: CdkDrag, drop: CdkDropList) => {
const dragData = drag.data as TreeNode;
const dropData = this.node; // Current node where we're trying to drop
// Prevent dropping on itself or its descendants and root
if (
dragData.id === dropData.id ||
this.isDescendant(dragData, dropData) ||
dropData.id === 'root'
) {
return false;
}
return true;
};
private isDescendant(dragNode: TreeNode, targetNode: TreeNode): boolean {
return targetNode.children.some(
(child: TreeNode) =>
child.id === dragNode.id || this.isDescendant(dragNode, child)
);
}
drop(event: CdkDragDrop) {
const draggedNode = event.item.data as TreeNode;
if (event.previousContainer === event.container) {
// Moving within the same container
moveItemInArray(
event.container.data,
event.previousIndex,
event.currentIndex
);
} else {
// Moving to a different container
const success = this.treeService.moveNode(
draggedNode.id,
this.node.id,
'inside',
event.currentIndex // Pass the current index for position-based insertion
);
if (success) {
console.log('Node moved successfully to:', {
targetNode: this.node.value,
position: event.currentIndex,
});
}
}
}
moveUpLevel() {
const currentParent = this.treeService.getParentNode(this.node.id);
if (!currentParent) {
console.log('Cannot move up: No parent found');
return;
}
const grandParent = this.treeService.getParentNode(currentParent.id);
if (!grandParent) {
console.log('Cannot move up: No grandparent found');
return;
}
// Find the index where the current parent is in the grandparent's children
const parentIndex = grandParent.children.findIndex(
(child: TreeNode) => child.id === currentParent.id
);
if (parentIndex === -1) {
console.log('Cannot move up: Parent index not found');
return;
}
// Move the node one level up
const success = this.treeService.moveNode(
this.node.id,
grandParent.id,
'inside',
parentIndex + 1 // Insert after the current parent
);
if (success) {
console.log('Node moved up successfully:', {
nodeId: this.node.id,
newParentId: grandParent.id,
position: parentIndex + 1,
});
}
}
// Update the tree service to include better logging
removeChild(childId: string) {
const index = this.node.children.findIndex(
(child: TreeNode) => child.id === childId
);
if (index !== -1) {
const removedNode = this.node.children[index];
this.node.children.splice(index, 1);
console.log('Removed child:', {
childId,
parentId: this.node.id,
parentValue: this.node.value,
});
}
}
addChild() {
const newNode: TreeNode = {
id: Date.now().toString(),
value: 'New Node',
children: [],
};
this.treeService.updateNodeMaps(newNode, this.node.id);
this.node.children.push(newNode);
}
deleteNode() {
this.onDelete.emit(this.node.id);
}
onDragStarted() {
document.body.classList.add('dragging');
}
onDragEnded() {
document.body.classList.remove('dragging');
}
onRegisterDropList(childDropListId: string) {
if (!this.dropListIds.includes(childDropListId)) {
this.dropListIds.push(childDropListId);
this.registerDropList.emit(childDropListId);
}
}
}
< /code>
// tree.service.ts
import { Injectable } from '@angular/core';
import { TreeNode } from '@shared/interfaces/tree-node.model';
@Injectable({
providedIn: 'root',
})
export class TreeService {
private nodeMap = new Map();
private parentMap = new Map();
getRegisteredNodes(): string[] {
return Array.from(this.nodeMap.keys());
}
updateNodeMaps(node: TreeNode, parentId?: string) {
console.log('Updating node maps:', {
nodeId: node.id,
parentId,
nodeValue: node.value,
});
this.nodeMap.set(node.id, node);
if (parentId) {
this.parentMap.set(node.id, parentId);
}
console.log('Current maps after update:', {
nodeMapSize: this.nodeMap.size,
parentMapSize: this.parentMap.size,
nodeMapKeys: Array.from(this.nodeMap.keys()),
parentMapKeys: Array.from(this.parentMap.keys()),
});
node.children.forEach((child: TreeNode) =>
this.updateNodeMaps(child, node.id)
);
}
findNodeById(id: string): TreeNode | undefined {
const node = this.nodeMap.get(id);
console.log('Finding node by id:', {
searchId: id,
found: !!node,
nodeValue: node?.value,
});
return node;
}
getParentNode(nodeId: string): TreeNode | undefined {
const parentId = this.parentMap.get(nodeId);
const parentNode = parentId ? this.nodeMap.get(parentId) : undefined;
console.log('Getting parent node:', {
childId: nodeId,
parentId,
foundParent: !!parentNode,
parentValue: parentNode?.value,
});
return parentNode;
}
private isDescendant(nodeId: string, targetId: string): boolean {
console.log('Checking if descendant:', {
nodeId,
targetId,
});
let currentNode = this.findNodeById(targetId);
let depth = 0;
while (currentNode && depth < 1000) {
console.log('Traversing up the tree:', {
currentNodeId: currentNode.id,
currentNodeValue: currentNode.value,
depth,
});
if (currentNode.id === nodeId) {
console.log('Found ancestor match - would create circular reference');
return true;
}
currentNode = this.getParentNode(currentNode.id);
depth++;
}
console.log('No circular reference found');
return false;
}
moveNode(
nodeId: string,
targetId: string,
position: 'before' | 'after' | 'inside',
insertIndex?: number
): boolean {
console.log('Starting moveNode operation:', {
nodeId,
targetId,
position,
insertIndex,
targetNodeValue: this.findNodeById(targetId)?.value,
});
const sourceNode = this.findNodeById(nodeId);
const targetNode = this.findNodeById(targetId);
if (!sourceNode || !targetNode) {
console.log('Move failed: Source or target node not found');
return false;
}
const sourceParent = this.getParentNode(nodeId);
if (!sourceParent) {
console.log('Move failed: Source parent not found');
return false;
}
// Check for circular reference
if (this.isDescendant(nodeId, targetId)) {
console.log('Move failed: Would create circular reference');
return false;
}
// Remove from old parent
sourceParent.children = sourceParent.children.filter(
(child: TreeNode) => child.id !== nodeId
);
// Add to new location
if (position === 'inside') {
if (typeof insertIndex === 'number' && insertIndex >= 0) {
// Insert at specific position
targetNode.children.splice(insertIndex, 0, sourceNode);
} else {
// Default behavior: append to end
targetNode.children.push(sourceNode);
}
this.parentMap.set(nodeId, targetId);
} else {
const targetParent = this.getParentNode(targetId);
if (!targetParent) {
console.log('Move failed: Target parent not found');
return false;
}
const targetIndex = targetParent.children.findIndex(
(child: TreeNode) => child.id === targetId
);
const insertPosition =
position === 'after' ? targetIndex + 1 : targetIndex;
targetParent.children.splice(insertPosition, 0, sourceNode);
this.parentMap.set(nodeId, targetParent.id);
}
console.log('Move completed successfully. New structure:', {
movedNodeId: nodeId,
newParentId: targetId,
newParentValue: targetNode.value,
insertPosition: insertIndex,
});
return true;
}
}
< /code>
// app-tree.component.ts (Parent component)
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
OnInit,
} from '@angular/core';
import { TreeComponent } from '../../components/branch-display/branch-display.component';
import { TreeNode } from '@shared/interfaces/tree-node.model';
import { TreeService } from '@shared/services/tree/tree.service';
import { mockTreeData } from '../../components/branch-display/mock-data';
@Component({
selector: 'app-tree-container',
standalone: true,
imports: [TreeComponent, CommonModule],
templateUrl: './branch-display-container.component.html',
styleUrls: ['./branch-display-container.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppTreeContainer implements OnInit {
treeData: TreeNode = mockTreeData;
dropListIds: string[] = [];
constructor(
private treeService: TreeService,
private changeDetectionRef: ChangeDetectorRef
) {}
ngOnInit() {
// Register all nodes in the tree with the service
this.registerNodesRecursively(this.treeData);
console.log('Tree Container Initialized');
}
private registerNodesRecursively(node: TreeNode, parentId?: string) {
// Register the current node
this.treeService.updateNodeMaps(node, parentId);
console.log('Registered node:', { nodeId: node.id, parentId });
// Register all children
node.children.forEach((child: TreeNode) => {
this.registerNodesRecursively(child, node.id);
});
}
onRegisterDropList(id: string) {
if (!this.dropListIds.includes(id)) {
this.dropListIds = [...this.dropListIds, id];
}
}
}
1739617623
Anonymous
Ich arbeite an einer dynamischen verschachtelten Baumstruktur mit angularem CDK -Drag & Drop. Die Drag-and-Drop-Funktion funktioniert auf der ersten Ebene, fällt jedoch fehl, wenn versucht wird, Elemente tiefer in der Hierarchie fallen zu lassen. Das CDKDropListDropped -Ereignis wird auf tieferen Ebenen nicht ausgelöst. (nach LV12) Demo: https://nested-- angular.netlify.app Codequelle: https: // GitHub. com/phamhung075/verschachtelter Baum Wie kann ich sicherstellen, dass Drag & drop auf allen verschachtelten Ebenen funktioniert? Vorschläge zum Beheben oder Debuggen dieses Problems?[code]// tree.component.ts import { CdkDrag, CdkDragDrop, CdkDropList, DragDropModule, moveItemInArray, } from '@angular/cdk/drag-drop'; import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { TreeNode } from '@shared/interfaces/tree-node.model'; import { TreeService } from '@shared/services/tree/tree.service'; @Component({ selector: 'app-tree', standalone: true, imports: [CommonModule, FormsModule, DragDropModule], templateUrl: './branch-display.component.html', styleUrls: ['./branch-display.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) export class TreeComponent implements OnInit { @Input() node!: TreeNode; @Input() isLastChild = false; @Input() isRoot = true; @Input() dropListIds: string[] = []; @Output() onDelete = new EventEmitter(); @Output() registerDropList = new EventEmitter(); dropListId = `drop-list-${Math.random().toString(36).substring(2)}`; private hasBeenInitialized = false; constructor( private treeService: TreeService, private changeDetectionRef: ChangeDetectorRef ) {} ngOnInit() { if (!this.hasBeenInitialized) { this.registerDropList.emit(this.dropListId); console.log('Tree Component Initialized:', { nodeId: this.node.id, value: this.node.value, children: this.node.children.length, }); this.hasBeenInitialized = true; } } canDrop = (drag: CdkDrag, drop: CdkDropList) => { const dragData = drag.data as TreeNode; const dropData = this.node; // Current node where we're trying to drop // Prevent dropping on itself or its descendants and root if ( dragData.id === dropData.id || this.isDescendant(dragData, dropData) || dropData.id === 'root' ) { return false; } return true; }; private isDescendant(dragNode: TreeNode, targetNode: TreeNode): boolean { return targetNode.children.some( (child: TreeNode) => child.id === dragNode.id || this.isDescendant(dragNode, child) ); } drop(event: CdkDragDrop) { const draggedNode = event.item.data as TreeNode; if (event.previousContainer === event.container) { // Moving within the same container moveItemInArray( event.container.data, event.previousIndex, event.currentIndex ); } else { // Moving to a different container const success = this.treeService.moveNode( draggedNode.id, this.node.id, 'inside', event.currentIndex // Pass the current index for position-based insertion ); if (success) { console.log('Node moved successfully to:', { targetNode: this.node.value, position: event.currentIndex, }); } } } moveUpLevel() { const currentParent = this.treeService.getParentNode(this.node.id); if (!currentParent) { console.log('Cannot move up: No parent found'); return; } const grandParent = this.treeService.getParentNode(currentParent.id); if (!grandParent) { console.log('Cannot move up: No grandparent found'); return; } // Find the index where the current parent is in the grandparent's children const parentIndex = grandParent.children.findIndex( (child: TreeNode) => child.id === currentParent.id ); if (parentIndex === -1) { console.log('Cannot move up: Parent index not found'); return; } // Move the node one level up const success = this.treeService.moveNode( this.node.id, grandParent.id, 'inside', parentIndex + 1 // Insert after the current parent ); if (success) { console.log('Node moved up successfully:', { nodeId: this.node.id, newParentId: grandParent.id, position: parentIndex + 1, }); } } // Update the tree service to include better logging removeChild(childId: string) { const index = this.node.children.findIndex( (child: TreeNode) => child.id === childId ); if (index !== -1) { const removedNode = this.node.children[index]; this.node.children.splice(index, 1); console.log('Removed child:', { childId, parentId: this.node.id, parentValue: this.node.value, }); } } addChild() { const newNode: TreeNode = { id: Date.now().toString(), value: 'New Node', children: [], }; this.treeService.updateNodeMaps(newNode, this.node.id); this.node.children.push(newNode); } deleteNode() { this.onDelete.emit(this.node.id); } onDragStarted() { document.body.classList.add('dragging'); } onDragEnded() { document.body.classList.remove('dragging'); } onRegisterDropList(childDropListId: string) { if (!this.dropListIds.includes(childDropListId)) { this.dropListIds.push(childDropListId); this.registerDropList.emit(childDropListId); } } } < /code> // tree.service.ts import { Injectable } from '@angular/core'; import { TreeNode } from '@shared/interfaces/tree-node.model'; @Injectable({ providedIn: 'root', }) export class TreeService { private nodeMap = new Map(); private parentMap = new Map(); getRegisteredNodes(): string[] { return Array.from(this.nodeMap.keys()); } updateNodeMaps(node: TreeNode, parentId?: string) { console.log('Updating node maps:', { nodeId: node.id, parentId, nodeValue: node.value, }); this.nodeMap.set(node.id, node); if (parentId) { this.parentMap.set(node.id, parentId); } console.log('Current maps after update:', { nodeMapSize: this.nodeMap.size, parentMapSize: this.parentMap.size, nodeMapKeys: Array.from(this.nodeMap.keys()), parentMapKeys: Array.from(this.parentMap.keys()), }); node.children.forEach((child: TreeNode) => this.updateNodeMaps(child, node.id) ); } findNodeById(id: string): TreeNode | undefined { const node = this.nodeMap.get(id); console.log('Finding node by id:', { searchId: id, found: !!node, nodeValue: node?.value, }); return node; } getParentNode(nodeId: string): TreeNode | undefined { const parentId = this.parentMap.get(nodeId); const parentNode = parentId ? this.nodeMap.get(parentId) : undefined; console.log('Getting parent node:', { childId: nodeId, parentId, foundParent: !!parentNode, parentValue: parentNode?.value, }); return parentNode; } private isDescendant(nodeId: string, targetId: string): boolean { console.log('Checking if descendant:', { nodeId, targetId, }); let currentNode = this.findNodeById(targetId); let depth = 0; while (currentNode && depth < 1000) { console.log('Traversing up the tree:', { currentNodeId: currentNode.id, currentNodeValue: currentNode.value, depth, }); if (currentNode.id === nodeId) { console.log('Found ancestor match - would create circular reference'); return true; } currentNode = this.getParentNode(currentNode.id); depth++; } console.log('No circular reference found'); return false; } moveNode( nodeId: string, targetId: string, position: 'before' | 'after' | 'inside', insertIndex?: number ): boolean { console.log('Starting moveNode operation:', { nodeId, targetId, position, insertIndex, targetNodeValue: this.findNodeById(targetId)?.value, }); const sourceNode = this.findNodeById(nodeId); const targetNode = this.findNodeById(targetId); if (!sourceNode || !targetNode) { console.log('Move failed: Source or target node not found'); return false; } const sourceParent = this.getParentNode(nodeId); if (!sourceParent) { console.log('Move failed: Source parent not found'); return false; } // Check for circular reference if (this.isDescendant(nodeId, targetId)) { console.log('Move failed: Would create circular reference'); return false; } // Remove from old parent sourceParent.children = sourceParent.children.filter( (child: TreeNode) => child.id !== nodeId ); // Add to new location if (position === 'inside') { if (typeof insertIndex === 'number' && insertIndex >= 0) { // Insert at specific position targetNode.children.splice(insertIndex, 0, sourceNode); } else { // Default behavior: append to end targetNode.children.push(sourceNode); } this.parentMap.set(nodeId, targetId); } else { const targetParent = this.getParentNode(targetId); if (!targetParent) { console.log('Move failed: Target parent not found'); return false; } const targetIndex = targetParent.children.findIndex( (child: TreeNode) => child.id === targetId ); const insertPosition = position === 'after' ? targetIndex + 1 : targetIndex; targetParent.children.splice(insertPosition, 0, sourceNode); this.parentMap.set(nodeId, targetParent.id); } console.log('Move completed successfully. New structure:', { movedNodeId: nodeId, newParentId: targetId, newParentValue: targetNode.value, insertPosition: insertIndex, }); return true; } } < /code> // app-tree.component.ts (Parent component) import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, } from '@angular/core'; import { TreeComponent } from '../../components/branch-display/branch-display.component'; import { TreeNode } from '@shared/interfaces/tree-node.model'; import { TreeService } from '@shared/services/tree/tree.service'; import { mockTreeData } from '../../components/branch-display/mock-data'; @Component({ selector: 'app-tree-container', standalone: true, imports: [TreeComponent, CommonModule], templateUrl: './branch-display-container.component.html', styleUrls: ['./branch-display-container.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppTreeContainer implements OnInit { treeData: TreeNode = mockTreeData; dropListIds: string[] = []; constructor( private treeService: TreeService, private changeDetectionRef: ChangeDetectorRef ) {} ngOnInit() { // Register all nodes in the tree with the service this.registerNodesRecursively(this.treeData); console.log('Tree Container Initialized'); } private registerNodesRecursively(node: TreeNode, parentId?: string) { // Register the current node this.treeService.updateNodeMaps(node, parentId); console.log('Registered node:', { nodeId: node.id, parentId }); // Register all children node.children.forEach((child: TreeNode) => { this.registerNodesRecursively(child, node.id); }); } onRegisterDropList(id: string) { if (!this.dropListIds.includes(id)) { this.dropListIds = [...this.dropListIds, id]; } } } [/code]