如何删除角度材质树中的节点

时间:2019-10-30 07:06:38

标签: angular tree angular-material

我现在正在实现带有材料树的树结构。

我的代码如下:

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方法,因为它无法通过调试找到父节点。

但是我想要一个按钮,它可以删除节点。

如何处理这种情况?

2 个答案:

答案 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);
      }
    }
  }