Creating dynamic tree using MatTree in Angular

Akshay Nayak
6 min readSep 5, 2020

Angular material provides a component Mat-Tree that helps to create Tree structure in Angular. In this post we will understand how do we create dynamic tree using Mat Tree component

First we will create basic tree as mentioned in the Angular Material Tree Docs. Later we will modify the code snippet to make the tree dynamic.

Below mentioned is the link of the Angular material document that explains about Mat Tree.

Lets create a basic mat tree first and then make it dynamic

Steps to create Basic Tree

Step 1: Create Angular Application.

After creating the application replace the content of app.component.html with

<h1>Welcome to tree component</h1>

Step 2: Create component in Angular application with name tree-component Replace content of tree-component.component.html with below code

<p>Welcome to tree child component</p>

This is how the application will look like.

Step 3: In order to access the component in app.component.html we need to add component in the declaration section of App Module.

Add component to the main module.

3.1 — Make below changes to the app.module.ts

a. Add import statement

import { TreeComponentComponent } from ‘./tree-component/tree-component.component’;

b. Add TreeComponentComponent in declaration array

declarations: [AppComponent,TreeComponentComponent]

3.2 — Make below changes to the app.component.html

Add <app-tree-component></app-tree-component> below <h1>Welcome to tree component</h1>. Updated code would look like this.

<h1>Welcome to tree component</h1><app-tree-component></app-tree-component>

Step 4: Adding Mat Tree component.

4.1 — Make below changes to the app.module.ts

a. Add import statement

import { MatSliderModule,MatButtonModule,MatTreeModule,MatIconModule } from ‘@angular/material’;

b. Add below classes to the imports array:

MatSliderModule,
MatButtonModule,
MatTreeModule,
MatIconModule

4.2 — Add below content to the tree-component.component.html file

<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<!?-?This is the tree node template for leaf nodes ?
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
<!?-?use a disabled button to provide padding for tree leaf ?
<button mat-icon-button disabled></button>
{{node.name}}
</mat-tree-node>
<!?-?This is the tree node template for expandable nodes ?
<mat-tree-node *matTreeNodeDef="let node;when: hasChild" matTreeNodePadding>
<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>
{{node.name}}
</mat-tree-node>
</mat-tree>

4.3 — Add below content of code in tree-component.component.ts

import { Component, OnInit } from '@angular/core';
import {FlatTreeControl} from '@angular/cdk/tree';
import {MatTreeFlatDataSource, MatTreeFlattener} from '@angular/material/tree';
interface FoodNode {
name: string;
children?: FoodNode[];
}
const TREE_DATA: FoodNode[] = [
{
name: 'Fruit',
children: [ {name: 'Apple'}, {name: 'Banana'}, {name: 'Fruit loops'}, ]
},
{
name: 'Vegetables',
children: [ { name: 'Green',
children: [ {name: 'Broccoli'}, {name: 'Brussels sprouts'}, ]
},
{
name: 'Orange',
children: [ {name: 'Pumpkins'}, {name: 'Carrots'}, ] },
]
},
];
/** Flat node with expandable and level information */
interface ExampleFlatNode {
expandable: boolean;
name: string;
level: number;
}
@Component({
selector: 'app-tree-component',
templateUrl: './tree-component.component.html',
styleUrls: ['./tree-component.component.scss']
})
export class TreeComponentComponent implements OnInit {
private _transformer = (node: FoodNode, level: number) => {
return {
expandable: !!node.children && node.children.length > 0,
name: node.name,
level: level,
};
}
treeControl = new FlatTreeControl<ExampleFlatNode>(
node => node.level, node => node.expandable);
treeFlattener = new MatTreeFlattener(
this._transformer, node => node.level, node => node.expandable, node => node.children);
dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
constructor() {
//this.dataSource.data = TREE_DATA;
}
hasChild = (_: number, node: ExampleFlatNode) => node.expandable;
ngOnInit() {
this.dataSource.data = TREE_DATA;
}
}

Note- Code snippet mentioned in the section 4.2 and 4.3 is from the Angular Documentation.

And now run your application. You should be able to see tree structure as shown below.

The data that is shown in the above screen is from the variable TREE_DATA in the tree-component.component.ts file. In this example the hierarchy of the data is static which is not the case in most of the scenarios.

In most of the cases we have hierarchical data saved in database and we have Id and ParentId columns to link create the hierarchy. Database even though it supports hierarchical queries (For e.g.Start with and Connect by in Oracle ), it will not be able to create JSON structure as used by TREE_DATA (nested childrens in the JSON structure) in above example.

Step 5- Thus in order to support this dynamic hierarchy requirement we change the structure of TREE_DATA as shown below.

5.1 — Comment const TREE_DATA variable from tree-component.component.ts and replace it with below code.

We have removed nested structure of JSON and created flat structure. Additionally we have 2 attributes Id and ParentId to every object.

ParentId for top level elements will null and for all other elements it will be Id of the parent element.

const TREE_DATA: FoodNodeFlat[] = [
{ name: 'Fruit', Id: 1, parentId:null },
{ name: 'Apple', Id: 2, parentId: 1 },
{ name: 'Banana', Id: 3, parentId: 1 },
{ name: 'Fruit loops', Id: 4, parentId: 1 },
{ name: 'Vegetables', Id: 5, parentId:null },
{ name: 'Green', Id: 6, parentId: 5 },
{ name: 'Broccoli', Id: 7, parentId: 6 },
{ name: 'Brussels sprouts', Id: 8, parentId: 6 },
{ name: 'Orange', Id: 9, parentId: 5 },
{ name: 'Pumpkins', Id: 10, parentId: 9 },
{ name: 'Carrots', Id: 11, parentId: 9 },
{ name: 'India', Id: 12, parentId: null },
{ name: 'Maharashtra', Id: 13, parentId: 12 },
{ name: 'Mumbai', Id: 14, parentId: 13 },
{ name: 'Karnataka', Id: 15, parentId: 12 },
{ name: 'Bangalore', Id: 16, parentId: 15 },]

5.2 — After replacing the TREE_DATA structure you will get compilation error. Replace interface FoodNote declaration with code below interface declaration for FoodNodeFlat.

interface FoodNodeFlat {
name: string;
Id: Number;
parentId: Number;
children?: FoodNodeFlat[];
}

5.3 — Change FoodNode to FoodNodeFlat in _transformer function

5.4 — Replace below code in ngOnInit() function

this.dataSource.data = TREE_DATA;

to

this.dataSource.data =this.treeConstruct(TREE_DATA);

5.5 — Add below code inside class TreeComponentComponent and below function ngOnInit()

//constructTree recursively iterates through the tree to create nested tree structure.
//We only need Id and parentId Columns in the flat data to construct this tree properly.
treeConstruct(treeData) {
let constructedTree = [];
for (let i of treeData) {
let treeObj = i;
let assigned = false;
this.constructTree(constructedTree, treeObj, assigned)
}
return constructedTree;
}
constructTree(constructedTree, treeObj, assigned) {if (treeObj.parentId == null) {
treeObj.children = [];
constructedTree.push(treeObj);
return true;
} else if (treeObj.parentId == constructedTree.Id) {
treeObj.children = [];
constructedTree.children.push(treeObj);
return true;
}
else {
if (constructedTree.children != undefined) {
for (let index = 0; index < constructedTree.children.length; index++) {
let constructedObj = constructedTree.children[index];
if (assigned == false) {
assigned = this.constructTree(constructedObj, treeObj, assigned);
}
}
} else {
for (let index = 0; index < constructedTree.length; index++) {
let constructedObj = constructedTree[index];
if (assigned == false) {
assigned = this.constructTree(constructedObj, treeObj, assigned);
}
}
}
return false;
}
}

Now run your code again and you should see same hierarchical data as seen below

Conclusion

First TREE_DATA structure that was used in the example had the entire hierarchy in the JSON structure. Since we want to support dynamic hierarchy we converted JSON structure to flat hierarchy and added Id and ParentId attributes to build hierarchy programatically. In this way we can build hierarchical tree

Hope you have enjoyed reading this article. Please let me know your comments or suggestions if any.

--

--