Dynamic recursive treeview using nested-mat-tree by fetching data from rest api and json file alternatively

I’m trying to populate a dynamic treeview using nested-mat-tree.

The tree view alternates between fetching data from an API and displaying child nodes from a JSON file as you toggle through the levels. Here’s a recap:

First Level:
API Call: Fetch data from Spring Boot.
Display Data: Show fetched data.
Toggle: Reveals child nodes from a JSON file.

Next Level:
Toggle JSON Data: Triggers an API call with different end-point based on condition.
API Call: Fetch more data.
Display Data: Show the newly fetched api data.

Further Levels:
Toggle API Data: Displays child nodes from JSON again.
Repeat: This process continues indefinitely.
This ensures that the tree view dynamically switches between API calls and JSON data at each level of expansion.

Please provide if any reference for this case.

treeview.html

<div class="split-container">
  <div class="subpanel">
    <!-- Header for the Tree View section -->
    <div class="subpanel-header">Tree-View </div>
    <!-- Body of the Tree View section -->
    <div class="subpanel-body" id="subpanel-body">
      <!-- Angular Material Tree component -->
      <mat-tree [dataSource]="dataSource" [treeControl]="treeControl" class="example-tree">
        <!-- Tree node template for leaf nodes -->

        <!-- Tree node template for expandable nodes -->
        <mat-nested-tree-node *matTreeNodeDef="let node; when: hasChild">
          <div class="mat-tree-node">
            <button class="mat-icon-button-custom" (click)="onParentToggle(node)">
              <mat-icon>{{ treeControl.isExpanded(node) ? 'remove' : 'add' }}</mat-icon>
            </button>
            <button mat-button class="trail-button" (click)="attrVisualization(node)">
              {{ node.name.type?.toUpperCase().slice(0, 3) }}
            </button>
            <mat-icon class="status-icon" [ngStyle]="{'color': getIconColor(node)}">fiber_manual_record</mat-icon>
            {{node.name.name}}
          </div>
          <ul [class.example-tree-invisible]="!treeControl.isExpanded(node)">
            <ng-container matTreeNodeOutlet></ng-container>
          </ul>
        </mat-nested-tree-node>

        <!-- Tree node template for leaf nodes -->
        <mat-nested-tree-node *matTreeNodeDef="let node">
          <div class="mat-tree-node" (click)="onChildToggle(node)">
            <button class="mat-icon-button-custom">
              <mat-icon>{{ treeControl.isExpanded(node) ? 'remove' : 'add' }}</mat-icon>
            </button>
            {{node.name.name}}

            <ul [class.example-tree-invisible]="!treeControl.isExpanded(node)" *ngIf="node.children">
              <ng-container matTreeNodeOutlet>
                <ul *ngFor="let child of node.children">
                  <button class="mat-icon-button-custom" (click)="onChildToggle(child)">
                     <mat-icon>{{ treeControl.isExpanded(child) ? 'remove' : 'add' }}</mat-icon>
                   </button>
                 {{child.name.name}}
                </ul>
              </ng-container>
            </ul>
            
          </div>
        </mat-nested-tree-node>

      </mat-tree>
    </div>
  </div>

treeviewcomponent.ts

export interface TreeNode {
  name: {
    [key: string]: any;
  };
  children?: TreeNode[];
  parentId?: string; // Ensure parentId is defined in the interface
  parentDetails?: TreeNode;
}

@Component({
  selector: 'app-treeview',
  templateUrl: './treeview.component.html',
  styleUrls: ['./treeview.component.css']
})
export class TreeviewComponent implements OnInit {
  openTreeView = false;
  selectedNode: TreeNode | null = null;
  LocationData: any;
  treeChildren: any;
  apiData: any;

  treeControl = new NestedTreeControl<TreeNode>(node => node.children);
  dataSource = new MatTreeNestedDataSource<TreeNode>();

  constructor(
    private router: Router,
    private treeviewService: TreeviewService,
    private popUpService: PopUpServiceService,
    private http: HttpClient
  ) { }

  ngOnInit(): void {
    this.apiData = this.popUpService.getData(); //this is api data called to display top level nodes
    console.log('apiData Data:', this.apiData);

    this.apiData = JSON.parse(this.apiData);

    this.treeviewService.fetchJsonData().subscribe((jsonData: any) => {
      console.log('JSON Data:', jsonData[this.apiData[0].type]);

      const combinedData: TreeNode[] = this.apiData.map((item: any) => ({
        name: item,
        children: this.createTreeChildren(item.id, jsonData[this.apiData[0].type])
      }));

      this.dataSource.data = combinedData;
      console.log('DataSource:', this.dataSource.data);
    });
  }

  createTreeChildren(parentId: string, childrenData: any[]): TreeNode[] {
    return childrenData.map((child: string) => ({
      name: { name: child },
      parentId: parentId // Add parentId to each child node
    }));
  }

  hasChild = (_: number, node: TreeNode) => !!node.children && node.children.length > 0;


  onParentToggle(node: TreeNode): void {
    console.log("Toggling node:", node.name?.['id']);

    if (this.treeControl.isExpanded(node)) {
      this.treeControl.collapse(node);
    } else {
      this.treeControl.expand(node);
    }

    if (this.hasChild(0, node)) {
      this.assignParentToChildren(node);
    }
  }

  onChildToggle(node: TreeNode): void {
    console.log('Child Node parentType:', node);
    console.log('Child Node parentID:', node.parentId);
    console.log('Child Node name:', node.name['name']);

    if (this.treeControl.isExpanded(node)) {
      this.treeControl.collapse(node);
    } else {
      this.treeControl.expand(node);

      // Fetch sub-child nodes using childDetailapi
      this.treeviewService.childDetailapi(node.name['name'], node.parentId || '')
        .subscribe(response => {
          console.log('detail view Response:', response);

          // Create sub-child nodes
          const subChildren: TreeNode[] = response.map((subChild: any) => ({
            name: subChild,
            parentId: node.parentId,
          }));

          // Check if node already has children, if so, add the new ones
          if (node.children) {
            node.children.push(...subChildren);
          } else {
            node.children = subChildren;
          }

          // Refresh the data source to update the view
          this.dataSource.data = this.dataSource.data.slice();
        }, error => {
          console.error('API Error:', error);
        });
    }
  }


  findParentNode(childNode: TreeNode): TreeNode | null {
    for (const parentNode of this.dataSource.data) {
      if (parentNode.children && parentNode.children.some(child => child.name['name'] === childNode.name['name'])) {
        return parentNode;
      }
    }
    return null;
  }

  assignParentToChildren(parentNode: TreeNode): void {
    if (parentNode.children && parentNode.children.length > 0) {
      parentNode.children.forEach(childNode => {
        childNode.parentDetails = parentNode;
        this.assignParentToChildren(childNode);
      });
    }
  }

  getIconColor(node: any): string {
    switch (node.type) {
      case 'trail':
        return 'green';
      case 'port':
        return 'purple';
      case 'ne':
        return 'orange';
      default:
        return 'RGB(146,206,18)';
    }
  }
}
 

treeview service


@Injectable({
  providedIn: 'root'
})
export class TreeviewService {

   private jsonUrl = 'assets/properties.json';

  constructor(private http: HttpClient) { }

   fetchJsonData(): Observable<any> {
     return this.http.get<any>(this.jsonUrl);
   }


   childDetailapi(childType: string, parentId: string): Observable<any> {
    let apiUrl = '';

    switch (childType) {
      case 'Trail':
        apiUrl = `http://localhost:8080/pipe/locationId/${parentId}`;
        console.log(apiUrl);
        break;
      case 'Physical Device':
        apiUrl = `http://localhost:8080/physicalDevice/locationId/${parentId}`;
        
        console.log(apiUrl);
        break;
      case 'Leased Line':
        apiUrl = `http://localhost:8080/leasedLine/locationId/${parentId}`;
        
        console.log(apiUrl);
        break;
      default:
        throw new Error('Unknown child type');
    }

    return this.http.get(apiUrl);
  }
  

}