Angular mat-tree使用Observable作为数据源丢失原始数据引用怎么办
问题原因
你直接将Observable对象传给CDK Tree的dataSource输入属性属于不符合API规范的用法,CDK Tree内部会自动将非DataSource类型的输入(包括Observable、普通数组)包装为内部的DataSource实现,这个包装过程会对源数据做深拷贝处理,因此模板中渲染得到的node对象是拷贝后的副本,和你原始TREE_DATA中的对象引用不一致,你用全等===判断自然无法匹配。
而使用ArrayDataSource的版本是直接传入原始数组的引用,内部没有额外的拷贝逻辑,所以引用比对可以正常通过。
解决方案
方案1:使用官方指定的嵌套树数据源(推荐)
使用Angular Material提供的MatTreeNestedDataSource来承接Observable返回的数据,直接将原始数据赋值给数据源的data属性即可保留引用:
import { MatTreeNestedDataSource } from '@angular/material/tree'; interface FoodNode { name: string; children?: FoodNode[]; } const TREE_DATA: FoodNode[] = [ { name: 'Fruit', children: [ {name: 'Apple'}, {name: 'Banana'}, {name: 'Fruit loops'}, ] }, { name: 'Vegetables', children: [ { name: 'Green', children: [ {name: 'Broccoli'}, {name: 'Brussels sprouts'}, ] }, { name: 'Orange', children: [ {name: 'Pumpkins'}, {name: 'Carrots'}, ] }, ] }, ]; @Component({ selector: 'cdk-tree-nested-example', templateUrl: 'cdk-tree-nested-example.html', styleUrls: ['cdk-tree-nested-example.css'], }) export class CdkTreeNestedExample { treeControl = new NestedTreeControl<FoodNode>(node => node.children); dataSource = new MatTreeNestedDataSource<FoodNode>(); // 自行维护原始数据的引用,用于后续查找、初始化选中状态 private treeData = TREE_DATA; constructor() { // 模拟你的Observable数据源场景 of(this.treeData).subscribe(data => { this.dataSource.data = data; }); } hasChild = (_: number, node: FoodNode) => !!node.children && node.children.length > 0; testClick(node) { console.log(this.findNode(this.treeData, node)); } findNode(data: FoodNode[] | FoodNode, node: FoodNode) { console.log('search') if(Array.isArray(data)) { return data.some(x => this.findNode(x, node)); } else { return data === node || (data.children ? this.findNode(data.children, node) : false); } } }
方案2:改用唯一标识匹配(彻底避免引用问题)
如果你的场景允许,建议给每个节点增加唯一ID字段,查找时用ID比对而非引用比对,彻底规避引用变化带来的问题,也更适合数据是从后端接口获取的场景:
interface FoodNode { id: number; // 新增唯一ID name: string; children?: FoodNode[]; } // 查找逻辑修改为按ID匹配 findNode(data: FoodNode[] | FoodNode, targetNode: FoodNode) { if(Array.isArray(data)) { return data.some(x => this.findNode(x, targetNode)); } else { return data.id === targetNode.id || (data.children ? this.findNode(data.children, targetNode) : false); } }
SelectionModel初始化说明
调整完数据源后,直接从你自行维护的treeData中查找需要默认选中的节点,调用SelectionModel.select()方法即可,此时数据源和模板渲染的节点引用一致,选中状态可以正常生效。
内容的提问来源于stack exchange,提问作者Guerric P
相关产品推荐
相关产品推荐

