web-dev-qa-db-fra.com

Angular Material mat-tree get checkbox values

J'utilise Angular Material v6.0 MatTreeModule (mat-tree) avec des cases à cocher. Mais j'ai du mal à trouver comment déterminer quels nœuds ont été vérifiés et quels nœuds n'ont pas été vérifiés. Dans l'exemple de Angular Material, ils donnent le très bon code source pour le configurer que j'ai pu configurer très bien.

Cependant, je ne peux pas déterminer quelles cases à cocher ont été cochées et lesquelles ne l'ont pas été. J'ai essayé pendant des heures d'essayer de comprendre cela sans succès.

Mon objectif est que les utilisateurs finaux cochent ou décochent les cases à cocher dans l'arborescence et à partir de là, je dois exécuter un processus après avoir effectué leurs sélections.

Mais je suis complètement coincé à essayer de déterminer quels nœuds d'arbre mat sont vérifiés et non vérifiés et il n'y a pas de bon exemple de travail que je puisse trouver n'importe où.

Le code source clé se trouve ici

Plus d'informations sur mat-tree se trouvent ici

Quelqu'un peut-il m'aider à déterminer si les cases à cocher ont été cochées?

Merci.

Par demande, j'ajoute le code ici à partir des deux fichiers de code:

app/tree-checklist-example.ts app/tree-checklist-example.html

Code source TypeScript de "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>

Comme mentionné précédemment pour voir le code source complet et une démonstration de celui-ci fonctionner, veuillez aller à: https://stackblitz.com/angular/gabkadkvybq?file=app%2Ftree-checklist-example.html

Merci.

4
Juan Vega

Je pense que la liste de sélection (dans votre lien de démonstration Stackblitz) est ce que vous voulez. Cela signifie que vous devez faire le suivi des sélections dans MatTree par vous-même: cela ne le fera pas pour vous car il ne sait pas comment gérer les MatCheckboxes que vous utilisez au nœuds.

Dans votre démo, ceci est accompli en utilisant/maintenant le SelectionModel (une collection de @angular/cdk/collections Qui ne fait pas partie de MatTree). L'exemple modifié de Stackblitz est ici (il suffit de sélectionner un nœud avec des enfants sur MatTree).

La partie importante de la démo est que chaque clic sur un MatCheckbox déclenche une @Output() change sur cette case à cocher qui est utilisé pour déclencher la méthode todoItemSelectionToggle, qui met à jour la 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 est une collection basée sur un Set, qui a été construite par l'équipe @angular Exactement pour être utilisée par les développeurs utilisant des composants qui permettent plusieurs sélections pour les aider à suivre les changements sur ces composants. Vous pouvez voir plus de détails sur cette collection ici: https://github.com/angular/components/blob/master/src/cdk/collections/selection-model.ts

Comme tout en javascript, il n'y a pas de magie ici, fondamentalement, son constructeur accepte un argument booléen pour définir si le SelectionModel<T> (C'est un générique) stockera plusieurs valeurs (true) ou une seule valeur. Il a également des méthodes pratiques comme sort(predicate?: (a: T, b: T) => number), select(...values: T[]) pour ajouter des objets, deselect(...values: T[]) pour supprimer des objets, toggle(o: T) pour ajouter (si ce n'est pas le cas ' t existent) ou supprimer (si elle existe déjà). En interne, les comparaisons sont, par défaut, faites par référence, donc {a:1} != {a:1}.

5
julianobrasil

Vous pouvez obtenir la valeur directement à partir de la liste de contrôle

values ​​= this.checklistSelection.selected

Cela vous renverra exactement la valeur de tous les éléments cochés

2
Bruce

L'exemple que vous avez mentionné utilise Angular Material Selection Model.

Lorsqu'une propriété = SelectionModel et type de modèle = TodoItemFlatNode. vous faites cela par ->

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

Maintenant, la propriété checklistSelection comprendrait et vous pouvez accéder à toutes ces méthodes.

changé, hasValue, isSelected, sélection, onChange, basculer etc.

Alors maintenant, vous pouvez appliquer votre logique de sélection en accédant aux méthodes ci-dessus.

exemple

this.checklistSelection.isSelected ?
0
saidutt

Pas exactement une réponse (je ne l'ai pas encore), mais puisque je traite le même problème atm et que je n'ai pas assez de réputation pour ajouter un commentaire, je posterai ceci ici (mods, n'hésitez pas à supprimer si c'est contre les règles ).

Il me semble que mat-tree est buggy. On en a déjà parlé ici: https://github.com/angular/material2/issues/114 , mais la solution fournie là-bas n'a toujours pas résolu un comportement étrange lors de la déconnexion/vérification des enfants/parents nœuds. Cela transfère probablement aux valeurs que vous obtiendrez de SelectionModel que jpavel a mentionné. Juste un avertissement puisque vous avez affaire à cette fonctionnalité.

0
icpero