如何为Angular Material 6.0.1的mat-tree组件实现过滤功能?
哈哈,我之前做项目的时候刚好遇到过这个需求!Angular Material的mat-tree确实没内置树形过滤功能,但咱们完全可以自己实现,而且思路不算复杂,我给你分享一个我常用的可行方案:
实现mat-tree过滤功能的完整方案
核心思路是递归过滤节点数据,同时保留匹配节点的所有父节点(让用户能看到匹配节点的层级关系),然后更新树的数据源,最后自动展开相关父节点来提升体验。
1. 搭建基础结构
先确保你的mat-tree已经正常运行,比如嵌套树用NestedTreeControl和MatTreeNestedDataSource,然后添加一个过滤输入框:
<mat-form-field appearance="fill" class="filter-input"> <mat-label>过滤节点</mat-label> <input matInput [(ngModel)]="filterText" (input)="onFilterInput()"> <mat-icon matSuffix>search</mat-icon> </mat-form-field> <!-- 你的mat-tree核心结构 --> <mat-tree [dataSource]="filteredDataSource" [treeControl]="treeControl"> <!-- 普通节点模板 --> <mat-nested-tree-node *matTreeNodeDef="let node" matTreeNodeToggle> <li class="mat-tree-node"> <button mat-icon-button matTreeNodeToggle> <mat-icon>{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}</mat-icon> </button> <!-- 可选:高亮匹配文本 --> <span *ngIf="isNodeMatch(node, filterText)" class="matched-text">{{node.name}}</span> <span *ngIf="!isNodeMatch(node, filterText)">{{node.name}}</span> </li> </mat-nested-tree-node> <!-- 包含子节点的模板 --> <mat-tree-node *matTreeNodeDef="let node; when: hasChild"> <li> <div class="mat-tree-node"> <button mat-icon-button matTreeNodeToggle> <mat-icon>{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}</mat-icon> </button> <span *ngIf="isNodeMatch(node, filterText)" class="matched-text">{{node.name}}</span> <span *ngIf="!isNodeMatch(node, filterText)">{{node.name}}</span> </div> <ul [class.example-tree-invisible]="!treeControl.isExpanded(node)"> <ng-container matTreeNodeOutlet></ng-container> </ul> </li> </mat-tree-node> </mat-tree>
2. 编写过滤核心逻辑
在组件类里,我们需要完成几个关键操作:保存原始数据、递归过滤节点、自动展开父节点:
import { Component } from '@angular/core'; import { NestedTreeControl } from '@angular/cdk/tree'; import { MatTreeNestedDataSource } from '@angular/material/tree'; // 定义你的节点类型 interface TreeItem { name: string; children?: TreeItem[]; } @Component({ selector: 'app-tree-filter', templateUrl: './tree-filter.component.html', styleUrls: ['./tree-filter.component.scss'] }) export class TreeFilterComponent { treeControl = new NestedTreeControl<TreeItem>(node => node.children); originalDataSource = new MatTreeNestedDataSource<TreeItem>(); filteredDataSource = new MatTreeNestedDataSource<TreeItem>(); filterText = ''; // 示例原始树数据 private readonly originalTreeData: TreeItem[] = [ { name: '前端框架', children: [ { name: 'Angular' }, { name: 'React' }, { name: 'Vue' } ] }, { name: '后端语言', children: [ { name: 'Node.js' }, { name: 'Python' }, { name: 'Java' } ] } ]; constructor() { this.originalDataSource.data = this.originalTreeData; this.filteredDataSource.data = [...this.originalTreeData]; } hasChild = (_: number, node: TreeItem) => !!node.children && node.children.length > 0; // 过滤输入触发的函数 onFilterInput() { const filterValue = this.filterText.trim().toLowerCase(); if (!filterValue) { // 清空过滤时恢复原始数据 this.filteredDataSource.data = [...this.originalTreeData]; this.treeControl.collapseAll(); // 可选:清空后折叠所有节点 return; } // 递归过滤节点 const filteredData = this.recursiveFilter([...this.originalTreeData], filterValue); this.filteredDataSource.data = filteredData; // 自动展开所有包含匹配节点的父节点 this.expandFilteredNodes(filteredData); } // 递归过滤的核心函数 private recursiveFilter(nodes: TreeItem[], filter: string): TreeItem[] { return nodes.map(node => { const clonedNode = { ...node }; // 递归处理子节点 if (clonedNode.children) { clonedNode.children = this.recursiveFilter(clonedNode.children, filter); } // 判断当前节点是否匹配,或者子节点有保留的(父节点需要显示) const isMatch = clonedNode.name.toLowerCase().includes(filter); const hasValidChildren = clonedNode.children && clonedNode.children.length > 0; return isMatch || hasValidChildren ? clonedNode : null; }).filter(node => node !== null) as TreeItem[]; } // 自动展开过滤后的节点 private expandFilteredNodes(nodes: TreeItem[]) { nodes.forEach(node => { this.treeControl.expand(node); if (node.children) { this.expandFilteredNodes(node.children); } }); } // 可选:判断节点是否匹配,用于高亮文本 isNodeMatch(node: TreeItem, filter: string): boolean { return node.name.toLowerCase().includes(filter.trim().toLowerCase()); } }
3. 可选优化:高亮匹配文本
给匹配的文本添加高亮样式,在SCSS文件中:
.matched-text { background-color: #ffeb3b; padding: 2px 4px; border-radius: 3px; }
注意事项
- 必须克隆原始数据进行过滤,避免修改原数据导致清空过滤时无法恢复
- 如果是平级树(Flat Tree),思路类似,只需调整过滤逻辑适配FlatTree的结构(比如处理
level属性) - 对于大数据量的树,建议添加防抖处理(比如用RxJS的
debounceTime),避免每次输入都触发过滤,提升性能
内容的提问来源于stack exchange,提问作者SatAj
相关产品推荐
相关产品推荐

