我现在正在实现带有材料树的树结构。
我的代码如下:
import { Component, Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { SelectionModel } from '@angular/cdk/collections';
import { FlatTreeControl } from '@angular/cdk/tree';
import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree';
/**
* Node for to-do item
*/
export class TodoItemNode {
public children: TodoItemNode[];
public item: string;
}
/** Flat to-do item node with expandable and level information */
export class TodoItemFlatNode {
public item: string;
public level: number;
public expandable: boolean;
}
export interface Operator {
value: string;
viewValue: string;
}
/**
* The Json object for to-do list data.
*/
const TREE_DATA = {
AND: {
'': '',
'OR': {}
}
};
/**
* Checklist database, it can build a tree structured Json object.
* Each node in Json object represents a to-do item or a category.
* If a node is a category, it has children items and new items can be added under the category.
*/
@Injectable()
export class ChecklistDatabase {
public dataChange = new BehaviorSubject<TodoItemNode[]>([]);
get data(): TodoItemNode[] {
return this.dataChange.value;
}
constructor() {
this.initialize();
}
public initialize(): void {
// Build the tree nodes from Json object. The result is a list of `TodoItemNode` with nested
// file node as children.
const data = this.buildFileTree(TREE_DATA, 0);
// Notify the change.
this.dataChange.next(data);
}
/**
* Build the file structure tree. The `value` is the Json object, or a sub-tree of a Json object.
* The return value is the list of `TodoItemNode`.
*/
public buildFileTree(obj: { [key: string]: any }, level: number): TodoItemNode[] {
return Object.keys(obj).reduce<TodoItemNode[]>((accumulator, key) => {
const value = obj[key];
const node = new TodoItemNode();
node.item = key;
if (value != null) {
if (typeof value === 'object') {
node.children = this.buildFileTree(value, level + 1);
} else {
node.item = value;
}
}
return accumulator.concat(node);
}, []);
}
/** Add an item to to-do list */
public insertItem(parent: TodoItemNode, name: string): void {
if (parent.children) {
parent.children.push({ item: name } as TodoItemNode);
this.dataChange.next(this.data);
}
}
public deleteItem(parent: TodoItemNode, name: string): void {
if (parent.children) {
parent.children.pop();
this.dataChange.next(this.data);
}
}
public updateItem(node: TodoItemNode, name: string): void {
node.item = name;
this.dataChange.next(this.data);
}
}
@Component({
selector: 'app-dynamic-filter',
templateUrl: './dynamic-filter.component.html',
styleUrls: ['./dynamic-filter.component.scss'],
providers: [ChecklistDatabase]
})
export class DynamicFilterComponent {
/** Map from flat node to nested node. This helps us finding the nested node to be modified */
public flatNodeMap = new Map<TodoItemFlatNode, TodoItemNode>();
/** Map from nested node to flattened node. This helps us to keep the same object for selection */
public nestedNodeMap = new Map<TodoItemNode, TodoItemFlatNode>();
/** A selected parent node to be inserted */
public selectedParent: TodoItemFlatNode | null = null;
/** The new item's name */
public newItemName = '';
public treeControl: FlatTreeControl<TodoItemFlatNode>;
public treeFlattener: MatTreeFlattener<TodoItemNode, TodoItemFlatNode>;
public dataSource: MatTreeFlatDataSource<TodoItemNode, TodoItemFlatNode>;
/** The selection for checklist */
public checklistSelection = new SelectionModel<TodoItemFlatNode>(true /* multiple */);
public operators: Operator[] = [
{ value: 'EQ', viewValue: 'EQ' },
{ value: 'NEQ', viewValue: 'NEQ' },
{ value: 'GT', viewValue: 'GT' },
{ value: 'GTE', viewValue: 'GTE' },
{ value: 'LT', viewValue: 'LT' },
{ value: 'LTE', viewValue: 'LTE' },
{ value: 'REGEX', viewValue: 'REGEX' }
];
constructor(private _database: ChecklistDatabase) {
this.treeFlattener = new MatTreeFlattener(this.transformer, this.getLevel,
this.isExpandable, this.getChildren);
this.treeControl = new FlatTreeControl<TodoItemFlatNode>(this.getLevel, this.isExpandable);
this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
_database.dataChange.subscribe(data => {
this.dataSource.data = data;
});
}
public getLevel = (node: TodoItemFlatNode) => node.level;
public isExpandable = (node: TodoItemFlatNode) => node.expandable;
public getChildren = (node: TodoItemNode): TodoItemNode[] => node.children;
public hasChild = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.expandable;
public hasNoContent = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.item === '';
/**
* Transformer to convert nested node to flat node. Record the nodes in maps for later use.
*/
public transformer = (node: TodoItemNode, level: number) => {
const existingNode = this.nestedNodeMap.get(node);
const flatNode = existingNode && existingNode.item === node.item
? existingNode
: new TodoItemFlatNode();
flatNode.item = node.item;
flatNode.level = level;
flatNode.expandable = !!node.children;
this.flatNodeMap.set(flatNode, node);
this.nestedNodeMap.set(node, flatNode);
return flatNode;
}
/* Get the parent node of a node */
public getParentNode(node: TodoItemFlatNode): TodoItemFlatNode | null {
const currentLevel = this.getLevel(node);
if (currentLevel < 1) {
return null;
}
const startIndex = this.treeControl.dataNodes.indexOf(node) - 1;
for (let i = startIndex; i >= 0; i--) {
const currentNode = this.treeControl.dataNodes[i];
if (this.getLevel(currentNode) < currentLevel) {
return currentNode;
}
}
return null;
}
/** Select the category so we can insert the new item. */
public addNewItem(node: TodoItemFlatNode): void {
const parentNode = this.flatNodeMap.get(node);
this._database.insertItem(parentNode, '');
this.treeControl.expand(node);
}
public deleteItem(node: TodoItemFlatNode): void {
const parentNode = this.flatNodeMap.get(node);
this._database.deleteItem(parentNode, '');
this.treeControl.expand(node);
}
/** Save the node to database */
public saveNode(node: TodoItemFlatNode, itemValue: string): void {
const nestedNode = this.flatNodeMap.get(node);
this._database.updateItem(nestedNode, itemValue);
}
}
和html:
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle matTreeNodePadding>
{{node.item}}
</mat-tree-node>
<mat-tree-node *matTreeNodeDef="let node; when: hasNoContent" matTreeNodePadding>
<mat-form-field>
<mat-label>Operator</mat-label>
<mat-select>
<mat-option *ngFor="let operator of operators" [value]="operator.value">
{{operator.viewValue}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<input matInput #itemValue placeholder="New item...">
</mat-form-field>
<button mat-icon-button (click)="deleteItem(node)">
<mat-icon>delete</mat-icon>
</button>
</mat-tree-node>
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
{{node.item}}
<button mat-icon-button (click)="addNewItem(node)">
<mat-icon>add</mat-icon>
</button>
<button mat-icon-button (click)="deleteItem(node)">
<mat-icon>delete</mat-icon>
</button>
</mat-tree-node>
</mat-tree>
我添加了一个名为deleteItem的函数,但是如何找到被单击的正确节点是一个问题。
也许我必须重新编程deleteItem方法,因为它无法通过调试找到父节点。
但是我想要一个按钮,它可以删除节点。
如何处理这种情况?
答案 0 :(得分:0)
要从“材料”树中删除子节点,可以执行以下操作:
组件:
public deleteItem(node: TodoItemFlatNode): void {
// Get the parent node of the selected child node
const parentNode = this.getParentNode(node);
// Map from flat node to nested node.
const parentFlat = this.flatNodeMap.get(parentNode);
this._database.deleteItem(parentFlat!, node.name);
this.treeControl.expand(node);
}
服务:
public deleteItem(parent: TodoItemNode, name: string): void {
if (parent.children) {
parent.children = parent.children.filter(c => c.name !== name);
this.dataChange.next(this.data);
}
}
答案 1 :(得分:0)
这是对我有用的方法:
removeItem(node: TodoItemFlatNode) {
let parentNode = this.flatNodeMap.get(node);
let flatNode = this.dataSource.data[0].children;
for (let i = flatNode.length - 1; i >= 0; i--) {
if (flatNode[i].item === node.item) {
if (parentNode.children) {
//if you want to warn user
}
this.dataChange.value[0].children.splice(i, 1);
this.flatNodeMap.delete(node);
this.dataChange.next(this.data);
}
}
}