Search code examples
angularangular-materialangular7

Mat-tree create a tree with values to display and ids


I'm learning how to use mat-tree following the docs, but now I need to use a data source tree that has not only strings defined in it, because when I click a node I need to know its id.
This is the stackblitz of my project.
Basically I need to use this tree with checkboxes that I've built following the docs, but now my data source is this:

const TREE_DATA:TodoItemNode = {
  item: "First item",
  children: [{
    item: 'Second item',
    children:[
      {
        item: "Third item",
        id: 3
      }
    ],
      id:2
    }],
  id:1
  }

So I want to display as text the value of item and then get the "id" when that node is clicked. Currently I can't make it work and I only see these tree nodes:

enter image description here

Is there a working example for data as JSON, with properties etc. as I want to achieve?


Solution

  • Really I beleive the example about tree-view is more complex. Imagine you has some like

    const TREE_DATA: FoodNode[] = [
      {
        name: "Fruit",
        children: [
          { name: "Apple", id: 1 },
          { name: "Banana", id: 2 },
          { name: "Fruit loops", id: 3 }
        ]
      },
      {
        name: "Vegetables",
        children: [
          {
            name: "Green",
            children: [
              { name: "Broccoli", id: 4 },
              { name: "Brussel sprouts", id: 5 }
            ]
          },
          {
            name: "Orange",
            children: [{ name: "Pumpkins", id: 6 }, { name: "Carrots", id: 7 }]
          }
        ]
      }
    ];
    

    If you add to your FoodNode interface some auxiliars properties: selected,parent and indeterminate

    interface FoodNode {
      name: string;
      id?: number;
      selected?: boolean;
      indeterminate?:boolean;
      parent?:FoodNode
      children?: FoodNode[];
    }
    

    The key here is know the "parent", so, when we toogle one check, ask about his parent

      setParent(data, parent) {
        data.parent = parent;
        if (data.children) {
          data.children.forEach(x => {
            this.setParent(x, data);
          });
        }
      }
    

    You can write an .html like

    <mat-tree [dataSource]="dataSource" [treeControl]="treeControl" class="example-tree">
      <!-- This is the tree node template for leaf nodes -->
      <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
        <li class="mat-tree-node">
          <!-- use a disabled button to provide padding for tree leaf -->
          <button mat-icon-button disabled></button>
              <mat-checkbox class="checklist-leaf-node" 
                (change)="todoItemSelectionToggle($event.checked,node)" 
                [checked]="node.selected"
    >{{node.name}}</mat-checkbox>
        </li>
      </mat-tree-node>
      <!-- This is the tree node template for expandable nodes -->
      <mat-nested-tree-node *matTreeNodeDef="let node; when: hasChild">
        <li>
          <div class="mat-tree-node">
            <button mat-icon-button matTreeNodeToggle
                    [attr.aria-label]="'toggle ' + node.name">
              <mat-icon class="mat-icon-rtl-mirror">
                {{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
              </mat-icon>
            </button>
                <mat-checkbox [checked]="node.selected"
                      [indeterminate]="node.indeterminate && !node.selected"
                      (change)="todoItemSelectionToggle($event.checked,node)">
                {{node.name}}</mat-checkbox>
          </div>
          <ul [class.example-tree-invisible]="!treeControl.isExpanded(node)">
            <ng-container matTreeNodeOutlet></ng-container>
          </ul>
        </li>
      </mat-nested-tree-node>
    </mat-tree>
    

    Anothers functions are

      treeControl = new NestedTreeControl<FoodNode>(node => node.children);
      dataSource = new MatTreeNestedDataSource<FoodNode>();
    
      constructor() {
        this.dataSource.data = TREE_DATA;
        Object.keys(this.dataSource.data).forEach(x => {
          this.setParent(this.dataSource.data[x], null);
        });
      }
    
      hasChild = (_: number, node: FoodNode) =>
        !!node.children && node.children.length > 0;
    
      checkAllParents(node) {
        if (node.parent) {
          const descendants = this.treeControl.getDescendants(node.parent);
          node.parent.selected=descendants.every(child => child.selected);
          node.parent.indeterminate=descendants.some(child => child.selected);
          this.checkAllParents(node.parent);
        }
      }
    
      todoItemSelectionToggle(checked, node) {
        node.selected = checked;
        if (node.children) {
          node.children.forEach(x => {
            this.todoItemSelectionToggle(checked, x);
          });
        }
        this.checkAllParents(node);
      }
    }
      submit() {
        let result = [];
        this.dataSource.data.forEach(node => {
          result = result.concat(
            this.treeControl
              .getDescendants(node)
              .filter(x => x.selected && x.id)
              .map(x => x.id)
          );
        });
        console.log(result);
      }
    

    The stackblitz, here