如何通过代码聚焦并高亮Material Tree指定节点
Mat Tree 根据ID查找节点时无法高亮子节点的解决方法
现有代码问题
递归查找函数
foundFolder: boolean = false; expand(folderId: number): any { this.treeControl.dataNodes.forEach(_node => { if (_node.id === folderId && !this.foundFolder) { this.treeControl.collapseAll(); this.treeControl.expandDescendants(_node); const element = document.getElementById(_node.name); element?.setAttribute("style", "background-color:#F6E8EC;"); this.foundFolder = true; } else if (_node.expandable && !this.foundFolder && _node.children.find(c => c.id === folderId)) { this.expand(_node.id); } }); }
问题:这段代码只会高亮父节点,找不到实际的目标子节点。需要准确定位并高亮任意层级的子节点,同时展开其路径。
现有Mat Tree结构
interface ExampleFlatNode { id: number; expandable: boolean; name: string; level: number; children: Folder[]; highlight: boolean; } private _transformer = (node: Folder, level: number) => { return { expandable: !!node.children && node.children.length > 0, id: node.id, name: node.name, level: level, children: node.children, }; }; treeControl = new FlatTreeControl<ExampleFlatNode>( node => node.level, node => node.expandable, ); treeFlattener = new MatTreeFlattener( this._transformer, node => node.level, node => node.expandable, node => node.children, ); dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
问题根源
当前逻辑的错误在于:当检测到父节点包含目标子节点时,递归调用expand(_node.id),这会让函数转而查找父节点,最终高亮的是父节点而非目标子节点。另外,直接操作DOM设置样式不符合Angular的响应式设计理念。
解决方案
1. 重构查找逻辑
直接递归遍历所有节点找到目标ID,然后展开其所有祖先节点,最后标记高亮。
2. 使用属性绑定控制高亮
利用ExampleFlatNode中已有的highlight属性,在模板中绑定样式,避免直接操作DOM。
修改后的完整代码
查找与展开函数
expand(folderId: number): void { // 重置所有节点的高亮状态 this.resetAllHighlights(this.dataSource.data); this.treeControl.collapseAll(); // 递归查找目标节点 const target = this.findTargetNode(this.dataSource.data, folderId); if (target) { // 展开目标节点的所有祖先路径 this.expandNodePath(target.id); // 标记目标节点为高亮 target.highlight = true; // 滚动到目标节点(可选优化) setTimeout(() => { const element = document.getElementById(target.name); element?.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, 100); // 更新数据源触发视图刷新 this.dataSource.data = [...this.dataSource.data]; } } // 递归遍历所有节点找目标 private findTargetNode(nodes: Folder[], targetId: number): Folder | null { for (const node of nodes) { if (node.id === targetId) return node; if (node.children?.length) { const found = this.findTargetNode(node.children, targetId); if (found) return found; } } return null; } // 展开目标节点的所有祖先节点 private expandNodePath(targetId: number): void { const path = this.getNodeAncestors(this.treeControl.dataNodes, targetId); path.forEach(node => this.treeControl.expand(node)); } // 获取目标节点的所有祖先路径(扁平结构中) private getNodeAncestors(nodes: ExampleFlatNode[], targetId: number): ExampleFlatNode[] { const path: ExampleFlatNode[] = []; const traverse = (currentNodes: ExampleFlatNode[], id: number): boolean => { for (const node of currentNodes) { path.push(node); if (node.id === id) return true; if (node.expandable) { // 找到当前节点的子节点(根据层级关系筛选) const children = this.treeControl.dataNodes.filter(n => n.level === node.level + 1); if (traverse(children, id)) return true; } path.pop(); } return false; }; traverse(nodes, targetId); return path; } // 重置所有节点的高亮状态 private resetAllHighlights(nodes: Folder[]): void { nodes.forEach(node => { node.highlight = false; if (node.children?.length) this.resetAllHighlights(node.children); }); }
更新_transformer函数
确保highlight属性被正确转换到扁平节点:
private _transformer = (node: Folder, level: number) => { return { expandable: !!node.children && node.children.length > 0, id: node.id, name: node.name, level: level, children: node.children, highlight: node.highlight || false, // 新增高亮属性映射 }; };
模板与样式调整
在组件CSS中添加高亮样式:
.highlight-node { background-color: #F6E8EC; }
在Mat Tree模板中绑定样式:
<!-- 普通节点模板 --> <mat-tree-node *matTreeNodeDef="let node"> <li class="mat-tree-node"> <div [class.highlight-node]="node.highlight" id="{{node.name}}"> {{node.name}} </div> </li> </mat-tree-node> <!-- 可展开节点模板 --> <mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodeToggle> <li class="mat-tree-node"> <div [class.highlight-node]="node.highlight" id="{{node.name}}"> <button mat-icon-button matTreeNodeToggle> <mat-icon>{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}</mat-icon> </button> {{node.name}} </div> </li> </mat-tree-node>
关键优化点
- 直接递归查找目标节点,确保定位到真正的子节点而非父节点。
- 使用Angular响应式属性绑定控制样式,符合框架最佳实践。
- 重置所有节点高亮状态,避免残留高亮。
- 增加滚动到目标节点的逻辑,提升用户体验。
内容的提问来源于stack exchange,提问作者Behnam Faghih
相关产品推荐
相关产品推荐

