Angular 6 Material Tree разворачивание функционирует неправильно - PullRequest
0 голосов
/ 26 ноября 2018

В настоящее время я пытаюсь разработать древовидную структуру для динамических данных, используя компонент дерева угловых материалов, и я следовал приведенному ниже примеру кода:

https://stackblitz.com/edit/material-tree-dynamic

Поскольку дерево, которое яразработали не работает должным образом, я скопировал выше код, как он есть, и попытаться запустить на моем компьютере.но функциональность свертывания не работает.Вот мой файл машинописного текста (html точно такой же):

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; };


}

Когда я сверну корневой узел, имеющий более 1 дочернего уровня , этот результат будет получен

Так, ребята, кто-нибудь может сказать мне, что является причиной этого?И как я могу это исправить?Это было бы очень полезно.

1 Ответ

0 голосов
/ 22 февраля 2019

Почему это происходит

Причиной этого является способ реализации функции переключения.При свертывании узла (вызов toggleNode со значением false для параметра expand ) выполняется следующая строка:

this.data.splice(index + 1, children.length);

Структура данных Flat Tree, используемая в этом случае для хранилищ дерева материаловвсе его элементы в простом массиве вместе с атрибутом уровня для каждого узла.Таким образом, дерево может выглядеть так:

- Root (lvl: 1)
- Child1 (lvl: 2)
- Child2 (lvl: 2)
- Child1OfChild2 (lvl: 3)
- Child2OfChild2 (lvl: 3)
- Child3 (lvl: 2)

Обратите внимание, что дочерние элементы размещаются после родительского элемента в массиве при расширении узла.Когда узел свернут, дочерние элементы узла должны быть удалены из массива.В этом случае это работает, только если ни один из дочерних элементов не расширен и, таким образом, сам не имеет дочерних элементов.Понятно, почему это так, если мы снова посмотрим на строку кода, которую я упомянул выше:

При свертывании узла вызывается строка кода сверху.Функция splice удаляет определенное количество элементов, начиная с позиции, которая передается в первом параметре ( index + 1 , который является первым элементом после того, который мы сворачиваем).Количество удаляемых элементов передается во втором параметре (в данном случае children.length ).

При сворачивании Child2 в приведенном выше примере это будет работать нормально: элементы удаляются из позиции index + 1 (index - это позиция Child2).Поскольку у Child2 есть два потомка, children.length будет равно двум, что означает, что функция splice удалит именно Child1OfChild2 и Child2OfChild2 (так как index + 1 - это позиция Child1OfChild2).

Но скажем, например, что мы хотим свернуть корень в дереве примера сверху.В этом случае index + 1 будет позицией Child1, которая в порядке.Проблема в том, что children.length вернет 3, так как корень имеет только трех прямых потомков.Это приведет к удалению первых трех элементов массива, начиная с Child1, в результате чего Child2OfChild2 и Child3 будут по-прежнему находиться в массиве.

Решение

СпособЯ решил эту проблему, заменив проблемную строку кода следующей логикой:

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);

В первой строке я использую функцию slice, чтобы получить часть массива после сворачиваемого узладо конца массива.После этого я использую цикл for для подсчета количества элементов для этого подмассива, уровень которых выше, чем у узла, который мы свертываем (более высокий уровень означает, что они являются дочерними или внучатыми и т. Д.).Как только цикл встретится с узлом, уровень которого совпадает с тем, который мы сворачиваем, цикл остановится, и у нас будет счетчик, содержащий количество элементов, которые мы хотим удалить, начиная с первого элемента после узламы рушимсяУдаление элементов происходит в последней строке с использованием функции splice.

...