Angular 6 Material Tree collapse functionality is not working properly
目前,我正在尝试使用Angular材质树组件为动态数据开发树结构,并且遵循以下代码示例:
https://stackblitz.com/edit/material-tree-dynamic
由于我开发的树无法正常工作,因此我按原样复制了上面的代码,然后尝试在我的计算机上运行。 但收合功能无法使用。 这是我的打字稿文件(html完全一样):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | import {Component, Injectable} from '@angular/core'; import {FlatTreeControl} from '@angular/cdk/tree'; import {CollectionViewer, SelectionChange} from '@angular/cdk/collections'; import {BehaviorSubject} from 'rxjs/BehaviorSubject'; import {Observable} from 'rxjs/Observable'; import {merge} from 'rxjs/observable/merge'; import {map} from 'rxjs/operators/map'; /** Flat node with expandable and level information */ export class DynamicFlatNode { constructor(public item: string, public level: number = 1, public expandable: boolean = false, public isLoading: boolean = false) {} } /** * Database for dynamic data. When expanding a node in the tree, the data source will need to fetch * the descendants data from the database. */ export class DynamicDatabase { dataMap = new Map([ ['Simulation', ['Factorio', 'Oxygen not included']], ['Indie', [`Don't Starve`, 'Terraria', 'Starbound', 'Dungeon of the Endless']], ['Action', ['Overcooked']], ['Strategy', ['Rise to ruins']], ['RPG', ['Magicka']], ['Magicka', ['Magicka 1', 'Magicka 2']], [`Don't Starve`, ['Region of Giants', 'Together', 'Shipwrecked']] ]); rootLevelNodes = ['Simulation', 'Indie', 'Action', 'Strategy', 'RPG']; /** Initial data from database */ initialData(): DynamicFlatNode[] { return this.rootLevelNodes.map(name => new DynamicFlatNode(name, 0, true)); } getChildren(node: string): string[] | undefined { return this.dataMap.get(node); } isExpandable(node: string): boolean { return this.dataMap.has(node); } } /** * File database, it can build a tree structured Json object from string. * Each node in Json object represents a file or a directory. For a file, it has filename and type. * For a directory, it has filename and children (a list of files or directories). * The input will be a json object string, and the output is a list of `FileNode` with nested * structure. */ @Injectable() export class DynamicDataSource { dataChange: BehaviorSubject<DynamicFlatNode[]> = new BehaviorSubject<DynamicFlatNode[]>([]); get data(): DynamicFlatNode[] { return this.dataChange.value; } set data(value: DynamicFlatNode[]) { this.treeControl.dataNodes = value; this.dataChange.next(value); } constructor(private treeControl: FlatTreeControl<DynamicFlatNode>, private database: DynamicDatabase) {} connect(collectionViewer: CollectionViewer): Observable<DynamicFlatNode[]> { this.treeControl.expansionModel.onChange!.subscribe(change => { if ((change as SelectionChange<DynamicFlatNode>).added || (change as SelectionChange<DynamicFlatNode>).removed) { this.handleTreeControl(change as SelectionChange<DynamicFlatNode>); } }); return merge(collectionViewer.viewChange, this.dataChange).pipe(map(() => this.data)); } /** Handle expand/collapse behaviors */ handleTreeControl(change: SelectionChange<DynamicFlatNode>) { if (change.added) { change.added.forEach((node) => this.toggleNode(node, true)); } if (change.removed) { change.removed.reverse().forEach((node) => this.toggleNode(node, false)); } } /** * Toggle the node, remove from display list */ toggleNode(node: DynamicFlatNode, expand: boolean) { const children = this.database.getChildren(node.item); const index = this.data.indexOf(node); if (!children || index < 0) { // If no children, or cannot find the node, no op return; } if (expand) { node.isLoading = true; setTimeout(() => { const nodes = children.map(name => new DynamicFlatNode(name, node.level + 1, this.database.isExpandable(name))); this.data.splice(index + 1, 0, ...nodes); // notify the change this.dataChange.next(this.data); node.isLoading = false; }, 1000); } else { this.data.splice(index + 1, children.length); this.dataChange.next(this.data); } } } @Component({ selector: 'app-audience-tree', templateUrl: './audience-tree.component.html', styleUrls: ['./audience-tree.component.css'], providers: [DynamicDatabase] }) export class AudienceTreeComponent{ constructor(database: DynamicDatabase) { this.treeControl = new FlatTreeControl<DynamicFlatNode>(this.getLevel, this.isExpandable); this.dataSource = new DynamicDataSource(this.treeControl, database); this.dataSource.data = database.initialData(); } treeControl: FlatTreeControl<DynamicFlatNode>; dataSource: DynamicDataSource; getLevel = (node: DynamicFlatNode) => { return node.level; }; isExpandable = (node: DynamicFlatNode) => { return node.expandable; }; hasChild = (_: number, _nodeData: DynamicFlatNode) => { return _nodeData.expandable; }; } |
当我折叠具有多个子级的根节点时
这个结果将给出
伙计们,有人能告诉我原因吗? 我该如何解决? 这将是一个很大的帮助。
为什么会这样
这样做的原因是实现切换功能的方式。折叠节点时(对于expand参数,使用false调用
1 | this.data.splice(index + 1, children.length); |
在这种情况下,用于"材料树"的"平面树"数据结构将其所有元素以及每个节点的级别属性存储在一个简单的数组中。
因此,一棵树可能看起来像这样:
1 2 3 4 5 6 | - Root (lvl: 1) - Child1 (lvl: 2) - Child2 (lvl: 2) - Child1OfChild2 (lvl: 3) - Child2OfChild2 (lvl: 3) - Child3 (lvl: 2) |
请注意,展开节点时,子元素会在数组中置于其父元素之后。当节点折叠时,应从阵列中删除该节点的子元素。在这种情况下,仅当没有一个子项扩展并且因此具有子项本身时,此方法才起作用。很清楚,如果我们再次看一下我上面提到的代码行,为什么会这样呢?
折叠节点时,将调用上面的代码行。
从上面的示例中折叠Child2时,这将正常工作:从位置index + 1(索引为Child2的位置)中删除了元素。由于Child2有两个孩子,children.length将为2,这意味着
但是说,例如,我们想从上方折叠示例树中的根。在这种情况下,索引+1将是Child1的位置,没关系。问题在于children.length将返回3,因为根只有三个直接子代。
这将导致删除从Child1开始的数组的前三个元素,导致Child2OfChild2和Child3仍在数组中。
解
我解决此问题的方法是用以下逻辑替换有问题的代码行:
1 2 3 4 5 6 7 8 9 | const afterCollapsed: ArtifactNode[] = this.data.slice(index + 1, this.data.length); let count = 0; for (count; count < afterCollapsed.length; count++) { const tmpNode = afterCollapsed[count]; if (tmpNode.level <= node.level){ break; } } this.data.splice(index+1, count); |
在第一行中,我使用
使用