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.
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;
}
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?