9 votes

Angular Material mat-tree obtient les valeurs des cases à cocher

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é cochés et quels nœuds n'ont pas été cochés. Dans l'exemple d'Angular Material, ils donnent un très bon code source pour le mettre en place, ce que j'ai pu faire sans problème.

Cependant, je ne parviens pas à déterminer quelles cases ont été cochées et lesquelles ne l'ont pas été. J'ai essayé pendant des heures de résoudre ce problème, sans succès.

Mon objectif est que les utilisateurs finaux cochent ou décochent les cases de l'arbre et, à partir de là, je dois exécuter un processus après qu'ils ont fait leurs sélections.

Mais je suis complètement coincé en essayant de comprendre quels nœuds de mat-tree sont vérifiés et non vérifiés et il n'y a pas de bon exemple fonctionnel que j'ai pu trouver nulle part.

Le code source de la clé se trouve aquí

Vous trouverez plus d'informations sur mat-tree aquí

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

Merci.

Conformément à la demande, j'ajoute le code ici à partir des deux fichiers de code :

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

Le 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 son fonctionnement, veuillez vous rendre sur le site : https://stackblitz.com/angular/gabkadkvybq?file=app%2Ftree-checklist-example.html

Merci.

7voto

Bruce Points 61

Vous pouvez obtenir la valeur directement à partir de la sélection des listes de contrôle.

values = this.checklistSelection.selected

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

6voto

jpavel Points 733

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 en MatTree par vous-même : il ne le fera pas pour vous parce qu'il ne sait pas comment gérer la MatCheckbox es que vous utilisez aux nœuds.

Dans votre démo, ceci est accompli en utilisant/maintenant l'interface de l'utilisateur. SelectionModel (une collection de @angular/cdk/collections qui ne fait pas partie de MatTree ). L'exemple modifié de Stackblitz est aquí (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 une MatCheckbox déclenche un @Output() change sur cette case à cocher qui est utilisé pour déclencher le todoItemSelectionToggle qui met à jour le 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);
  }

Le site SelectionModel est une collection basée sur un Set qui a été construit par le @angular est exactement destinée aux développeurs qui utilisent des composants permettant des sélections multiples, afin de les aider à suivre les modifications apportées à 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 la fonction SelectionModel<T> (c'est un générique) permet de stocker plusieurs valeurs (true) ou une seule valeur. Il dispose également de méthodes pratiques comme sort(predicate?: (a: T, b: T) => number) , select(...values: T[]) pour ajouter des objets, deselect(...values: T[]) pour retirer des objets, toggle(o: T) à ajouter (s'il n'existe pas) ou à supprimer (s'il existe déjà). En interne, les comparaisons sont, par défaut, effectuées par référence, donc {a:1} != {a:1} .

1voto

saidutt Points 98

L'exemple que vous avez mentionné utilise le modèle de sélection de matériaux angulaires.

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

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

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

changed, hasValue, isSelected, selection, onChange, toggle, etc.

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

exemple

this.checklistSelection.isSelected ?

0voto

icpero Points 1

Ce n'est pas exactement une réponse (je ne l'ai pas encore), mais comme je suis confronté au même problème en ce moment et que je n'ai pas assez de réputation pour ajouter un commentaire, je vais le poster ici (mods, n'hésitez pas à le supprimer si c'est contraire aux règles).

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X