Angular material mat-tree получить значения флажков

Я использую Angular Material v6.0 MatTreeModule (дерево матов) с флажками. Но мне сложно понять, как определить, какие узлы были проверены, а какие нет. В примере Angular Material они предоставляют очень хороший исходный код для его настройки, который я смог настроить отлично.

Однако я не могу определить, какие флажки установлены, а какие нет. Я часами пытался понять это, но безуспешно.

Моя цель - конечные пользователи будут устанавливать или снимать флажки в дереве, и оттуда мне нужно запустить какой-то процесс после того, как они сделают свой выбор.

Но я полностью застрял, пытаясь выяснить, какие узлы дерева матов проверены, а не проверены, и нет хорошего рабочего примера, который я мог бы где-либо найти.

Найден ключевой исходный код здесь

Больше информации о mat-tree можно найти здесь

Может ли кто-нибудь помочь, как определить, установлены ли флажки?

Спасибо.

По запросу я добавляю сюда код из двух файлов кода:

приложение / tree-checklist-example.ts приложение / tree-checklist-example.html

Исходный код Typescript из "app / tree-checklist-example.ts"

import {SelectionModel} from '@angular/cdk/collections';
import {FlatTreeControl} from '@angular/cdk/tree';
import {Component, Injectable} from '@angular/core';
import {MatTreeFlatDataSource, MatTreeFlattener} from '@angular/material/tree';
import {BehaviorSubject} from 'rxjs';

/**
 * Node for to-do item
 */
export class TodoItemNode {
  children: TodoItemNode[];
  item: string;
}

/** Flat to-do item node with expandable and level information */
export class TodoItemFlatNode {
  item: string;
  level: number;
  expandable: boolean;
}

/**
 * The Json object for to-do list data.
 */
const TREE_DATA = {
  Groceries: {
    'Almond Meal flour': null,
    'Organic eggs': null,
    'Protein Powder': null,
    Fruits: {
      Apple: null,
      Berries: ['Blueberry', 'Raspberry'],
      Orange: null
    }
  },
  Reminders: [
    'Cook dinner',
    'Read the Material Design spec',
    'Upgrade Application to Angular'
  ]
};

/**
 * 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 {
  dataChange = new BehaviorSubject<TodoItemNode[]>([]);

  get data(): TodoItemNode[] { return this.dataChange.value; }

  constructor() {
    this.initialize();
  }

  initialize() {
    // 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`.
   */
  buildFileTree(obj: object, 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 */
  insertItem(parent: TodoItemNode, name: string) {
    if (parent.children) {
      parent.children.push({item: name} as TodoItemNode);
      this.dataChange.next(this.data);
    }
  }

  updateItem(node: TodoItemNode, name: string) {
    node.item = name;
    this.dataChange.next(this.data);
  }
}

/**
 * @title Tree with checkboxes
 */
@Component({
  selector: 'tree-checklist-example',
  templateUrl: 'tree-checklist-example.html',
  styleUrls: ['tree-checklist-example.css'],
  providers: [ChecklistDatabase]
})
export class TreeChecklistExample {
  /** Map from flat node to nested node. This helps us finding the nested node to be modified */
  flatNodeMap = new Map<TodoItemFlatNode, TodoItemNode>();

  /** Map from nested node to flattened node. This helps us to keep the same object for selection */
  nestedNodeMap = new Map<TodoItemNode, TodoItemFlatNode>();

  /** A selected parent node to be inserted */
  selectedParent: TodoItemFlatNode | null = null;

  /** The new item's name */
  newItemName = '';

  treeControl: FlatTreeControl<TodoItemFlatNode>;

  treeFlattener: MatTreeFlattener<TodoItemNode, TodoItemFlatNode>;

  dataSource: MatTreeFlatDataSource<TodoItemNode, TodoItemFlatNode>;

  /** The selection for checklist */
  checklistSelection = new SelectionModel<TodoItemFlatNode>(true /* multiple */);

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

  getLevel = (node: TodoItemFlatNode) => node.level;

  isExpandable = (node: TodoItemFlatNode) => node.expandable;

  getChildren = (node: TodoItemNode): TodoItemNode[] => node.children;

  hasChild = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.expandable;

  hasNoContent = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.item === '';

  /**
   * Transformer to convert nested node to flat node. Record the nodes in maps for later use.
   */
  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;
  }

  /** Whether all the descendants of the node are selected */
  descendantsAllSelected(node: TodoItemFlatNode): boolean {
    const descendants = this.treeControl.getDescendants(node);
    return descendants.every(child => this.checklistSelection.isSelected(child));
  }

  /** Whether part of the descendants are selected */
  descendantsPartiallySelected(node: TodoItemFlatNode): boolean {
    const descendants = this.treeControl.getDescendants(node);
    const result = descendants.some(child => this.checklistSelection.isSelected(child));
    return result && !this.descendantsAllSelected(node);
  }

  /** Toggle the to-do item selection. Select/deselect all the descendants node */
  todoItemSelectionToggle(node: TodoItemFlatNode): void {
    this.checklistSelection.toggle(node);
    const descendants = this.treeControl.getDescendants(node);
    this.checklistSelection.isSelected(node)
      ? this.checklistSelection.select(...descendants)
      : this.checklistSelection.deselect(...descendants);
  }

  /** Select the category so we can insert the new item. */
  addNewItem(node: TodoItemFlatNode) {
    const parentNode = this.flatNodeMap.get(node);
    this.database.insertItem(parentNode!, '');
    this.treeControl.expand(node);
  }

  /** Save the node to database */
  saveNode(node: TodoItemFlatNode, itemValue: string) {
    const nestedNode = this.flatNodeMap.get(node);
    this.database.updateItem(nestedNode!, itemValue);
  }
}



HTML source code from "app/tree-checklist-example.html":

<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
  <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle matTreeNodePadding>
    <button mat-icon-button disabled></button>
    <mat-checkbox class="checklist-leaf-node"
                  [checked]="checklistSelection.isSelected(node)"
                  (change)="checklistSelection.toggle(node);">{{node.item}}</mat-checkbox>
  </mat-tree-node>

  <mat-tree-node *matTreeNodeDef="let node; when: hasNoContent" matTreeNodePadding>
    <button mat-icon-button disabled></button>
    <mat-form-field>
      <input matInput #itemValue placeholder="New item...">
    </mat-form-field>
    <button mat-button (click)="saveNode(node, itemValue.value)">Save</button>
  </mat-tree-node>

  <mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
    <button mat-icon-button matTreeNodeToggle
            [attr.aria-label]="'toggle ' + node.filename">
      <mat-icon class="mat-icon-rtl-mirror">
        {{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
      </mat-icon>
    </button>
    <mat-checkbox [checked]="descendantsAllSelected(node)"
                  [indeterminate]="descendantsPartiallySelected(node)"
                  (change)="todoItemSelectionToggle(node)">{{node.item}}</mat-checkbox>
    <button mat-icon-button (click)="addNewItem(node)"><mat-icon>add</mat-icon></button>
  </mat-tree-node>
</mat-tree>

Как упоминалось ранее, чтобы увидеть полный исходный код и его работающую демонстрацию, перейдите по ссылке: https://stackblitz.com/angular/gabkadkvybq?file=app%2Ftree-checklist-example.html

Спасибо.

Ссылка на ваш код хороша, но публикация в вашем сообщении минимальный воспроизводимый пример намного лучше. Это также доказывает ваши усилия по отладке.

Vega 10.09.2018 10:59

Привет @ juan-vega, мне очень интересен ответ, я действительно занимаюсь такими проблемами.

Jonathan Anctil 10.09.2018 16:44
8
2
16 745
4

Ответы 4

Я думаю, что список выбора (в вашей демо-ссылке Stackblitz) - это то, что вы хотите. Это означает, что вы должны отслеживать выбор в MatTree самостоятельно: он не сделает этого за вас, потому что не знает, как обрабатывать MatCheckboxes, которые вы используете на узлах.

В вашей демонстрации это достигается за счет использования / поддержки SelectionModel (коллекция @angular/cdk/collections, не являющаяся частью MatTree). Модифицированный пример Stackblitz - здесь (просто выберите какой-нибудь узел с детьми на MatTree).

Важной частью демонстрации является то, что каждый щелчок по MatCheckbox запускает @Output() changeна этом флажке, который используется для запуска метода todoItemSelectionToggle, который обновляет SelectionModel:

/** Toggle the to-do item selection. Select/deselect all the descendants node */
todoItemSelectionToggle(node: TodoItemFlatNode): void {
  // HERE IS WHERE THE PART OF THE MODEL RELATED TO THE CLICKED CHECKBOX IS UPDATED
  this.checklistSelection.toggle(node); 

  // HERE WE GET POTENTIAL CHILDREN OF THE CLICKED NODE
  const descendants = this.treeControl.getDescendants(node);

  // HERE IS WHERE THE REST OF THE MODEL (POTENTIAL CHILDREN OF THE CLICKED NODE) IS UPDATED
  this.checklistSelection.isSelected(node) 
    ? this.checklistSelection.select(...descendants)
    : this.checklistSelection.deselect(...descendants);
}

SelectionModel - это коллекция, основанная на Set, которая была создана командой @angular именно для использования разработчиками, использующими компоненты, которые позволяют множественный выбор, чтобы помочь им отслеживать изменения в этих компонентах. Вы можете увидеть более подробную информацию об этой коллекции здесь: https://github.com/angular/components/blob/master/src/cdk/collections/selection-model.ts

Как и все в javascript, здесь нет никакого волшебства, в основном, его конструктор принимает логический аргумент, чтобы определить, будет ли SelectionModel<T> (это универсальный вариант) хранить несколько значений (true) или одно значение. Он также имеет удобные методы, такие как sort(predicate?: (a: T, b: T) => number), select(...values: T[]) для добавления объектов, deselect(...values: T[]) для удаления объектов, toggle(o: T) для добавления (если он не существует) или удаления (если он уже существует). Внутренне сравнения по умолчанию выполняются по ссылке, поэтому {a:1} != {a:1}.

Огромное спасибо. Я очень ценю твой ответ. После нескольких часов дня в воскресенье я терял надежду, что не смогу использовать MatTree. Но не больше. Это именно то, что мы ищем, и это решение сделает это возможным.

Juan Vega 12.09.2018 06:28
stackoverflow.com/questions/52578743/… выручайте
user9286969 05.10.2018 10:53

URL демонстрации StackBlitz не работает @jpavel

yaswanthkoneri 26.06.2019 14:58

@yaswanthkoneri, я исправил ссылку. По какой-то причине я удалил свой пример stackblitz. Я не уверен, что я сделал в этом примере, но я думаю, что это было просто демонстрация того, как получить значения, выбранные на некоторых узлах дерева. Если да, то перестраиваю пример ..

julianobrasil 28.06.2019 12:02

@JuanVega, есть ли у вас последняя рабочая версия (источник stackblitz или аналогичный), поскольку я ищу такое же разрешение? Спасибо.

Efron A. 10.01.2020 04:06

ссылка снова не работает @julianobrasil

Packet Tracer 03.02.2020 15:06

Не совсем ответ (у меня его еще нет), но поскольку я имею дело с той же проблемой банкомата, и у меня недостаточно репутации, чтобы добавить комментарий, я опубликую его здесь (моды, не стесняйтесь удалять, если это противоречит правилам ).

Мне кажется, коврик глючит. Об этом уже говорилось здесь: https://github.com/angular/material2/issues/11400, но решение, предоставленное там, все еще не решало странное поведение при отключении / проверке дочерних / родительских узлов. Это, вероятно, переходит к значениям, которые вы получите от SelectionModel, упомянутого jpavel. Просто предупреждаем, так как вы имеете дело с этой функцией.

@icepero, я бы не стал классифицировать такое поведение как ошибку mat-tree. Пример определенно содержит ошибки, но, похоже, это не вина mat-tree. Попробую (не в ближайшие 2 недели) исправить пример. Это внесет дополнительную сложность в метод todoItemSelectionToggle, но я согласен, что это обязательная вещь.

julianobrasil 23.09.2018 12:44

В приведенном вами примере используется модель выбора материала Angular.

Когда свойство = SelectionModel и тип модели = TodoItemFlatNode. вы делаете это ->

    /** The selection for checklist */
  checklistSelection = new SelectionModel<TodoItemFlatNode>(true);

Теперь свойство checklistSelection будет содержать, и вы можете получить доступ ко всем этим методам.

изменено, hasValue, isSelected, selection, onChange, toggle и т. д.

Итак, теперь вы можете применить свою логику выбора, обратившись к приведенным выше методам.

пример

this.checklistSelection.isSelected ?

Вы можете получить значение прямо из контрольного списка.

values ​​= this.checklistSelection.selected

Это вернет вам точное значение всех отмеченных пунктов.

Другие вопросы по теме