title: Angular 6 Material Tree view
body: <p>I'm using Angular Material v6. I'm trying to create a tree dynamically from a JSON file on a server. Currently, the tree can create a parent but it does not create children. The console correctly outputs the parent and children. However, I can't seem to find the source of this issue.</p>
  
  <p>Here's my code:</p>
  
  <p><strong>testcasefolder-list.component.html:</strong></p>
  
  <pre class="lang-html prettyprint-override"><code>&lt;mat-tree [dataSource]="nestedDataSource" [treeControl]="nestedTreeControl" class="example-tree"&gt;
    &lt;mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle&gt;
      &lt;li class="mat-tree-node"&gt;
        &lt;button mat-icon-button disabled&gt;&lt;/button&gt;
        {{node.category.name}}
      &lt;/li&gt;
    &lt;/mat-tree-node&gt;
  
    &lt;mat-nested-tree-node *matTreeNodeDef="let node; when: hasNestedChild"&gt;
      &lt;li&gt;
        &lt;div class="mat-tree-node"&gt;
          &lt;button mat-icon-button matTreeNodeToggle
                  [attr.aria-label]="'toggle ' + node.filename"&gt;
            &lt;mat-icon class="mat-icon-rtl-mirror"&gt;
              {{nestedTreeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
            &lt;/mat-icon&gt;
          &lt;/button&gt;
          {{node.category.name}}
        &lt;/div&gt;
        &lt;ul [class.example-tree-invisible]="!nestedTreeControl.isExpanded(node)"&gt;
          &lt;ng-container matTreeNodeOutlet&gt;&lt;/ng-container&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/mat-nested-tree-node&gt;
  &lt;/mat-tree&gt;
  </code></pre>
  
  <p><strong>testcasefolder-list.component.ts:</strong></p>
  
  <pre class="lang-ts prettyprint-override"><code>import { TestcasefolderService } from './../../services/testcasefolder-service';
  import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
  import { NestedTreeControl } from '@angular/cdk/tree';
  import { MatTreeNestedDataSource } from '@angular/material';
  import { of } from 'rxjs';
  import { ITestcasefolder } from '../../model/itestcasefolder';
  export class CategoryNode {
    children: CategoryNode[ ];
    category: ITestcasefolder;
  }
  @Component({
    selector: 'app-testcasefolder-list',
    templateUrl: './testcasefolder-list.component.html',
    styleUrls: ['./testcasefolder-list.component.css']
  })
  export class TestcasefolderListComponent implements OnInit {
  // Does not follow main/sub category - just has a simple category!
  @Input() categoryID: number;
  @Output() categoryIDChange: EventEmitter&lt;number&gt; = new EventEmitter&lt;number&gt;();
  
  nestedTreeControl: NestedTreeControl&lt;CategoryNode&gt;;
  nestedDataSource: MatTreeNestedDataSource&lt;CategoryNode&gt;;
  type: any;
  hasNestedChild = (_: number, nodeData) =&gt; !(nodeData.type);
  
  constructor(
    private dataBrokerService: TestcasefolderService
  ) {
    this.nestedTreeControl = new NestedTreeControl(node =&gt; of(node.children));
    this.nestedDataSource = new MatTreeNestedDataSource();
  }
  
  ngOnInit() {
    this.dataBrokerService.getTestcasefolders().subscribe(categories =&gt; this.nestedDataSource.data = this.buildTree(categories));
  }
  
  onChange() {
    this.categoryIDChange.emit(this.categoryID);
  }
  
  private buildTree(categories: ITestcasefolder[]): CategoryNode[] {
  
    const tree: CategoryNode[] = [];
    const map: {[s: number]: CategoryNode} = {};
  
    // Build an index of the nodes
    categories.forEach(cat =&gt; {
      map[cat.id] = {children: [], category: cat};
    });
    console.log(tree);
    // Start adding nodes to tree
    for (const key of Object.keys(map)) {
      const catNode = map[+key];
  
      if (map[catNode.category.parentid]) {
        // Add it to the parent
        map[catNode.category.parentid].children.push(catNode);
      } else {
        // Add it to root
        tree.push(catNode);
      }
    }
    console.log(tree);
    return tree;
  }
  
  }
  </code></pre>
  
  <p><strong>testcasefolder-service.ts:</strong></p>
  
  <pre class="lang-ts prettyprint-override"><code>import { Injectable } from '@angular/core';
  import { HttpClient, HttpHeaders } from '@angular/common/http';
  import { catchError, map, tap } from 'rxjs/operators';
  import { of, Observable } from 'rxjs';
  import { MessageService } from './message-service.service';
  import { ITestcasefolder } from '../model/itestcasefolder';
  import { Global } from '../shared/Global';
  
  
  const httpOptions = {
    headers: new HttpHeaders({ 'Content-Type': 'application/json' })
  };
  
  @Injectable({
    providedIn: 'root'
  })
  export class TestcasefolderService {
  
    private testcasefolderServiceUrl = Global.BASE_USER_ENDPOINT +  'testcasefolders';
  
    constructor(private http: HttpClient, private messageService: MessageService) { }
  
    private log(message: string) {
      this.messageService.add(`TestcasefolderService: ${message}`);
    }
  
  
    getTestcasefolders(): Observable&lt;ITestcasefolder[]&gt; {
    return this.http.get&lt;ITestcasefolder[]&gt;(this.testcasefolderServiceUrl)
      .pipe(
        tap(requirements =&gt; this.log('fetched testcasefolder')),
        catchError(this.handleError('getTestcasefolders', []))
      );
   }
  
   getTestcasefolder(id: string): Observable&lt;ITestcasefolder&gt; {
    const url = `${this.testcasefolderServiceUrl}/${id}`;
    return this.http.get&lt;ITestcasefolder&gt;(url).pipe(
      tap(_ =&gt; this.log(`fetched testcasefolder id=${id}`)),
      catchError(this.handleError&lt;ITestcasefolder&gt;(`get testcasefolder id=${id}`))
    );
   }
  
  addTestcasefolder(testcasefolder: ITestcasefolder): Observable&lt;ITestcasefolder&gt; {
  delete testcasefolder['id'];
   return this.http.post&lt;ITestcasefolder&gt;(this.testcasefolderServiceUrl, testcasefolder, httpOptions)
  // tslint:disable-next-line:no-shadowed-variable
  .pipe(tap((testcasefolder: ITestcasefolder) =&gt; this.log(`added testcasefolder w/ id= ${testcasefolder.id}`)),
  catchError(this.handleError&lt;ITestcasefolder&gt;('addTestcasefolder'))
  );
  }
  
  updateTestcasefolder(testcasefolder: ITestcasefolder): Observable&lt;any&gt; {
    const url = `${this.testcasefolderServiceUrl}/${testcasefolder.id}`;
    return this.http.put(url, testcasefolder, httpOptions)
    .pipe(tap(_ =&gt; this.log(`updated testcasefolder id=${testcasefolder.id}`)),
    catchError(this.handleError&lt;any&gt;('updateTestcasefolder')));
  }
  deleteTestcasefolder(testcasefolder: ITestcasefolder |  string ): Observable&lt;ITestcasefolder&gt; {
  const id = typeof testcasefolder === 'string' ? testcasefolder : testcasefolder.id;
  const url = `${this.testcasefolderServiceUrl}/${id}`;
  return this.http.delete&lt;ITestcasefolder&gt;(url, httpOptions)
  .pipe(
    tap(_ =&gt; this.log(`deleted testcasefolder`)),
    catchError(this.handleError&lt;ITestcasefolder&gt;(`deletedTestcasefolder`))
  );
  }
  
   private handleError&lt;T&gt; (operation = 'operation', result?: T) {
     return (error: any): Observable&lt;T&gt; =&gt; {
  
       // TODO: send the error to remote logging infrastructure
       console.error(error); // log to console instead
  
       // TODO: better job of transforming error for user consumption
       this.log(`${operation} failed: ${error.message}`);
  
       // Let the app keep running by returning an empty result.
       return of(result as T);
     };
   }
  }
  </code></pre>
  
  <p><strong>itestcasefolder.ts:</strong></p>
  
  <pre class="lang-ts prettyprint-override"><code>export class ITestcasefolder {
      id?: string;
      name?: string;
      description?: string;
      parentid?: string;
      children?: ITestcasefolder[];
  }
  </code></pre>