如何保存并恢复Angular Material树形组件(mat tree)的展开状态?恢复后节点无法切换的问题排查
解决Angular Material Tree恢复展开后节点无法切换的问题
你遇到的问题根源在于直接保存了expansionModel.selected的引用,而这个集合会在调用collapseAll()时被内部修改,导致后续恢复的节点状态和组件内部的SelectionModel不同步,最终无法触发切换操作。
问题分析
当你执行this.prevExpansionModel = this.treeControl.expansionModel.selected时,实际上拿到的是SelectionModel内部维护的选中项数组的关联引用。调用collapseAll()后,这个集合里的节点会被从选中状态移除,此时你保存的prevExpansionModel里的节点对象,其在SelectionModel中的状态已经是未选中,但你通过expand()强制展开了节点,这就造成了视图状态和内部选中模型不一致,所以点击切换时组件无法正确响应。
正确解决方案:保存节点唯一标识而非对象引用
最可靠的方式是保存每个展开节点的唯一标识(比如id),而不是直接保存节点对象。这样无论数据源是否有变更,都能精准找到对应的节点并恢复状态。
步骤1:确保你的节点模型有唯一标识
假设你的NavbarNode接口包含唯一id字段:
interface NavbarNode { id: number; // 或其他唯一标识字段,比如uuid name: string; children?: NavbarNode[]; }
步骤2:保存展开节点的唯一ID
// 保存所有展开节点的ID,而不是节点对象 this.prevExpandedNodeIds = this.treeControl.expansionModel.selected.map(node => node.id); // 折叠所有节点 this.treeControl.collapseAll();
步骤3:递归查找节点并恢复展开状态
需要写一个递归函数遍历树形数据源,找到对应ID的节点后展开:
// 递归查找指定ID的节点 private findNodeById(nodes: NavbarNode[], targetId: number): NavbarNode | null { for (const node of nodes) { if (node.id === targetId) { return node; } // 递归查找子节点 if (node.children && node.children.length > 0) { const foundNode = this.findNodeById(node.children, targetId); if (foundNode) return foundNode; } } return null; } // 恢复展开状态 restoreExpansionState() { this.prevExpandedNodeIds.forEach(nodeId => { const targetNode = this.findNodeById(this.yourTreeDataSource, nodeId); // 替换成你的树形数据源变量名 if (targetNode) { this.treeControl.expand(targetNode); } }); }
备选方案:复制选中节点数组(适用于数据源稳定场景)
如果你的树形数据源不会动态变更(比如是静态数据),也可以通过复制数组的方式避免引用问题:
// 复制选中节点数组,切断和SelectionModel的引用关联 this.prevExpansionModel = [...this.treeControl.expansionModel.selected]; this.treeControl.collapseAll(); // 恢复时直接展开 this.prevExpansionModel.forEach(node => { this.treeControl.expand(node); });
这种方式的前提是数据源中的节点对象引用始终不变,否则还是会出现状态不一致的问题。
内容的提问来源于stack exchange,提问作者C0mpl3x
相关产品推荐
相关产品推荐

