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

如何通过代码聚焦并高亮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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 14:20:58