Angular 6 Material Nested Tree is not working with dynamic data

我与影子孤独终老i 提交于 2019-12-18 19:46:28

问题


I am using mat-tree with mat-nested-tree-node in Angular 6. What I want is to load the data dynamically when the user toggles expand icon.

Using the dynamic data example of Flat Tree given in Material Examples I have tried to use the same concept for Nested Tree. This is what I have tried so far https://stackblitz.com/edit/angular-naarcp

But it only shows the data which was prepopulated in the data array although in the console it is clear that data is getting updated but it never gets shown on the UI.

It recursively calls the _getChildren method for the nodes parent, child1, child2, child3 because this is initial data. I am adding My Child in child1 and child3 when user expands it but the added node is never shown.

I can not add dynamic children in _getChildren because it gets called recursively till last node.

Note:

I dont want to use Flat tree because it manages everything in single array and updating the single array gets really difficult in asynchronous loading of data

Help

Is there anything I am missing or nested trees are designed to work this way?


回答1:


I struggled when I implemented back when it first came out and found the UI wasnt updating because changes to an objects's properties do not get picked up by change detection. Please read through my original question and answer here. It is for a flattened tree but might save you hours of banging your head.

Why is my angular app becoming very slow after changing the data backing a mat-tree?




回答2:


Add Remove Update element with Angular 6 Nested Tree Material

addmodifymilestone.component.ts

    import { Component, Injectable, AfterViewInit, ViewChild } from '@angular/core';
import { NestedTreeControl } from '@angular/cdk/tree';
import { MatTreeNestedDataSource } from '@angular/material/tree';
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';


/**
 * Json node data with nested structure. Each node has a filename and a value or a list of children
 */
export class ItemNode {
  children: ItemNode[];
  filename: string;
  type: any;
  expanded: boolean;
}


@Component({
  selector: 'app-addmodifymilestone',
  templateUrl: './addmodifymilestone.component.html',
  styleUrls: ['./addmodifymilestone.component.scss'],
})
export class AddmodifymilestoneComponent implements AfterViewInit {
  @ViewChild('tree') tree;
  updateNodeItemName: any = 'null';
  updateItemNameInput: any;
  nestedTreeControl: NestedTreeControl<ItemNode>;
  nestedDataSource: MatTreeNestedDataSource<ItemNode>;
  dataChange: BehaviorSubject<ItemNode[]> = new BehaviorSubject<ItemNode[]>([]);


  constructor() {
    this.nestedTreeControl = new NestedTreeControl<ItemNode>(this._getChildren);
    this.nestedDataSource = new MatTreeNestedDataSource();

    this.dataChange.subscribe(data => this.nestedDataSource.data = data);
    this.dataChange.next([
      // {
      //   filename: 'Milestones',
      //   type: '',
      //   'expanded': false,
      //   children: [
      //     {
      //       filename: 'Milestone1',
      //       type: '',
      //       'expanded': false,
      //       children: []
      //     }
      //   ]
      // }
    ]);

    // this.dataChange.next([
    //   {
    //     filename: 'Milestones',
    //     type: '',
    //     children: [
    //       {
    //         filename: 'Milestone1',
    //         type: '',
    //         children: [
    //           {
    //             filename: 'To do list',
    //             type: '',
    //             children: [
    //               {
    //                 filename: 'Suggestion',
    //                 type: 'suggetion1, suggestion 2',
    //                 children: []
    //               }
    //             ],
    //           },
    //         ],
    //       }
    //     ],
    //   },
    // ]);
  }


  private _getChildren = (node: ItemNode) => {
    return observableOf(node.children);
  }


  hasNestedChild = (_: number, nodeData: ItemNode) => {
    return !(nodeData.type);
  }

  ngAfterViewInit(): void {
    this.nestedTreeControl = new NestedTreeControl<ItemNode>(this._getChildren);
    this.nestedDataSource = new MatTreeNestedDataSource();

    this.dataChange.subscribe(data => this.nestedDataSource.data = data);
  }

  changeState(node) {
    console.log('change state called :::');
    node.expanded = !node.expanded;
    console.log(node);
  }

  addNewMilestone() {
    this.nestedTreeControl = new NestedTreeControl<ItemNode>(this._getChildren);
    this.nestedDataSource = new MatTreeNestedDataSource();
    this.dataChange.subscribe(data => this.nestedDataSource.data = data);

    let tempData: any[];
    this.dataChange.subscribe(data => tempData = data);
    const data = new ItemNode();
    data.filename = 'New Milestone';
    data.type = '',
      data.children = [
        {
          filename: 'AddToDoList',
          type: 'AddToDoList',
          'expanded': false,
          children: [],
        },
        {
          filename: 'To do list',
          type: '',
          'expanded': false,
          children: [
            {
              filename: 'AddSuggestion',
              type: 'AddSuggestion',
              'expanded': false,
              children: [],
            },
            {
              filename: 'suggestions',
              type: 'suggestion1, suggestion2, suggestion3',
              'expanded': false,
              children: []
            },
          ],
        },
      ];

    tempData.push(data);
    this.dataChange.next(tempData);
  }


  addNewToDoList(node: ItemNode) {
    this.nestedTreeControl = new NestedTreeControl<ItemNode>(this._getChildren);
    this.nestedDataSource = new MatTreeNestedDataSource();

    this.dataChange.subscribe(data => this.nestedDataSource.data = data);

    const nodeChiledren: any[] = node.children;
    const data = {
      filename: 'To do list',
      type: '',
      children: [
        {
          filename: 'AddSuggestion',
          type: 'AddSuggestion',
          'expanded': false,
          children: [],
        }
      ],
    };
    nodeChiledren.push(data);

    let tempData: any[];
    this.dataChange.subscribe(data => tempData = data);

    tempData = tempData.map((value, index, array) => {
      if (value.filename === node.filename) {
        value.children = nodeChiledren;
      }
      return value;
    });


    const dataStringfy = JSON.stringify(tempData);
    const json: ItemNode[] = JSON.parse(dataStringfy);
    this.dataChange.next(json);
  }

  addNewSuggestion(node: ItemNode) {
    this.nestedTreeControl = new NestedTreeControl<ItemNode>(this._getChildren);
    this.nestedDataSource = new MatTreeNestedDataSource();

    this.dataChange.subscribe(data => this.nestedDataSource.data = data);

    const nodeChiledren: any[] = node.children;
    const data = {
      filename: 'Suggestion',
      type: 'suggestion11, suggestion22',
      'expanded': false,
      children: [],
    };
    nodeChiledren.push(data);

    let tempData: any[];
    this.dataChange.subscribe(data => tempData = data);

    tempData = tempData.map((value, index, array) => {
      if (value.filename === node.filename) {
        value.children = nodeChiledren;
      }
      return value;
    });


    const dataStringfy = JSON.stringify(tempData);
    const json: ItemNode[] = JSON.parse(dataStringfy);
    this.dataChange.next(json);
  }


  enableUpdateNode(node: ItemNode) {
    console.log('updateNode :::');
    console.log(node);
    this.updateNodeItemName = node.filename;
  }

  updateNode(node: ItemNode) {

    this.updateNodeItemName = 'null';
    console.log(this.updateItemNameInput);
    console.log(node);


    this.nestedTreeControl = new NestedTreeControl<ItemNode>(this._getChildren);
    this.nestedDataSource = new MatTreeNestedDataSource();

    this.dataChange.subscribe(data => this.nestedDataSource.data = data);

    node.filename = this.updateItemNameInput;
    let tempData: any[];
    this.dataChange.subscribe(data => tempData = data);

    tempData = tempData.map((value, index, array) => {
      if (value.filename === node.filename) {
        value = node;
      }
      return value;
    });


    const dataStringfy = JSON.stringify(tempData);
    const json: ItemNode[] = JSON.parse(dataStringfy);
    this.dataChange.next(json);

  }

  deleteNode(node: any) {

    this.nestedTreeControl = new NestedTreeControl<ItemNode>(this._getChildren);
    this.nestedDataSource = new MatTreeNestedDataSource();

    this.dataChange.subscribe(data => this.nestedDataSource.data = data);

    node.filename = this.updateItemNameInput;
    let tempData: any[];
    this.dataChange.subscribe(data => tempData = data);

    const index = tempData.findIndex(value => value.filename === node.filename);

    tempData.splice(index, 1);


    const dataStringfy = JSON.stringify(tempData);
    const json: ItemNode[] = JSON.parse(dataStringfy);
    this.dataChange.next(json);

  }
}

addmodifymilestone.component.html

<p class="paragraphMargingLeft">Add New Milestone <i class="fa fa-plus-square" aria-hidden="true" (click)="addNewMilestone()"></i></p>
<mat-tree #tree [dataSource]="nestedDataSource" [treeControl]="nestedTreeControl" class="example-tree">
  <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
    <li class="mat-tree-node" *ngIf="node.filename !== 'AddToDoList'  &&  node.filename !== 'AddSuggestion'">
      <button mat-icon-button disabled></button>
      {{node.filename}}: {{node.type }}
      <i class="fa fa fa-pencil" aria-hidden="true"></i>
      <i class="fa fa-trash" aria-hidden="true"></i>
    </li>
  </mat-tree-node>


  <mat-nested-tree-node *matTreeNodeDef="let node; when: hasNestedChild">
    <!-- {{node | json}} -->
    <li>

      <div class="mat-tree-node">
        <button mat-icon-button [attr.aria-label]="'toggle ' + node.name" (click)="changeState(node)">
          <mat-icon class="mat-icon-rtl-mirror">
            {{node.expanded ? 'expand_more' : 'chevron_right'}}
          </mat-icon>
        </button>
        <div *ngIf="updateNodeItemName !==  node.filename else updateable">
          {{node.filename}}
        </div>
        <ng-template #updateable>
          <mat-form-field>
            <input matInput  [(ngModel)]="updateItemNameInput" (change)="updateNode(node)" placeholder="Update Item">
          </mat-form-field>

        </ng-template>

        <i class="fa fa fa-pencil" aria-hidden="true" (click)="enableUpdateNode(node)"></i>
        <i class="fa fa-trash" aria-hidden="true" (click)="deleteNode(node)"></i>
        <!-- <button mat-icon-button (click)="addNewItem(node)"><mat-icon>add</mat-icon></button> -->
      </div>
      <ul [class.example-tree-invisible]="node.expanded">

        <div *ngFor="let data of node.children">
          <div *ngIf="data.filename === 'AddToDoList'">
            <p class="paragraphMargingLeft">Add To do list <i class="fa fa-plus-square" aria-hidden="true" (click)="addNewToDoList(node)"></i></p>
          </div>
          <div *ngIf="data.filename === 'AddSuggestion'">
            <p class="paragraphMargingLeft">Add Suggestion<i class="fa fa-plus-square" aria-hidden="true" (click)="addNewSuggestion(node)"></i></p>
          </div>
        </div>

        <ng-container matTreeNodeOutlet></ng-container>
      </ul>
    </li>
  </mat-nested-tree-node>
</mat-tree>

addmodifymilestone.component.scss

.example-tree-invisible {
  display: none;
}

.example-tree ul,
.example-tree li {
  margin-top: 0;
  margin-bottom: 0;
  list-style-type: none;
}

.example-tree li {
  margin-left: 25px;
}

.fa {
margin:5px;
}

.fa-trash{
color: red;
}

.mat-tree-node {
  display: flex;
  align-items: center;
  min-height: 0px;
  flex: 1;
  overflow: hidden;
  word-wrap: break-word;
}

.paragraphMargingLeft{
  margin-left: 46px;
}


来源:https://stackoverflow.com/questions/51764673/angular-6-material-nested-tree-is-not-working-with-dynamic-data

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!