I am trying to do a material angular tree based off of the docs but seem to have run into an error that I can't seem to diagnose. Chromes gives an error that Cannot read property 'children' of undefined
at line 8 of my HTML. It appears as though the tree gets loaded into nestedDataSource
just fine, but it won't display anything. Admittedly, I'm not entirely sure how the Angular Material tree works so I may have just messed up the html.
On angular 6.01
category-picker.component.html:
<mat-tree [dataSource]="nestedDataSource" [treeControl]="nestedTreeControl" class="category-picker">
<mat-tree-node *matTreeNodeDef="let node">
<li class="mat-tree-node">
{{node.category.name}}
</li>
</mat-tree-node>
<mat-nested-tree-node *matTreeNodeDef="let node; when: node.children.length">
<li>
{{node.category.name}}
<ul [class.example-tree-invisible]="!nestedTreeControl.isExpanded(node)">
<ng-container matTreeNodeOutlet></ng-container>
</ul>
</li>
</mat-nested-tree-node>
</mat-tree>
category-picker.component.ts:
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import {NestedTreeControl} from '@angular/cdk/tree';
import {MatSelectModule} from '@angular/material';
import {MatTreeNestedDataSource} from '@angular/material/tree';
import { DataBrokerService } from '../data-broker.service';
import { Category } from '../datatypes'
import { observable, of } from 'rxjs';
export class CategoryNode {
children: CategoryNode[];
category: Category;
}
@Component({
selector: 'app-category-picker',
templateUrl: './category-picker.component.html',
styleUrls: ['./category-picker.component.scss']
})
export class CategoryPickerComponent implements OnInit {
// Does not follow main/sub category - just has a simple category!
@Input() categoryID: number;
@Output() categoryIDChange:EventEmitter<number> = new EventEmitter<number>();
nestedTreeControl: NestedTreeControl<CategoryNode>;
nestedDataSource: MatTreeNestedDataSource<CategoryNode>;
constructor(
private dataBrokerService: DataBrokerService
) {
this.nestedTreeControl = new NestedTreeControl(node => of(node.children));
this.nestedDataSource = new MatTreeNestedDataSource();
}
ngOnInit() {
this.dataBrokerService.getCategories().subscribe(categories => this.nestedDataSource.data = this.buildTree(categories));
}
onChange(){
this.categoryIDChange.emit(this.categoryID);
}
private buildTree(categories:Category[]):CategoryNode[]{
console.log("tree");
let tree:CategoryNode[] = [];
let map: {[s:number]: CategoryNode} = {};
// Build an index of the nodes
categories.forEach(cat => {
map[cat.id] = {children:[], category:cat};
});
// Start adding nodes to tree
for (let key of Object.keys(map)){
let catNode = map[+key];
if (map[catNode.category.parent_id]){
// Add it to the parent
map[catNode.category.parent_id].children.push(catNode);
}
else{
// Add it to root
tree.push(catNode);
}
};
console.log(tree);
return tree;
}
}
It turns out I had incorrectly implemented the 'when
' function of MatTreeNodeDef
. In my HTML I needed to change when: node.children.length
to when: hasNestedChild
which is defined as = (_: number, nodeData) => {return nodeData.children.length}
in the .ts file. In other words, when is a required parameter that takes a function with two inputs (number and node).
category-picker.component.html:
<mat-nested-tree-node *matTreeNodeDef="let node; when: hasNestedChild">
category-picker.component.ts:
hasNestedChild=(_: number, nodeData) => {return nodeData.children.length}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With