Angular material: Hierarchy of interconnected elements within a tree structure

Hey there! I've been attempting to showcase a functional tree using Angular material, following the example at . However, it's not working as expected and I'm stuck trying to figure out why. Any help would be greatly appreciated! 😊

.html

<mat-tree [dataSource]="dataSource" [treeControl]="treeControl" class="example-tree">
  <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
    <li class="mat-tree-node">
      <button mat-icon-button disabled class="nav-item"></button>
      {{node.name}}
    </li>
  </mat-tree-node>
  <mat-nested-tree-node *matTreeNodeDef="let node; when: hasChild">
    <li>
      <div class="mat-tree-node">
        <button mat-icon-button matTreeNodeToggle
                [attr.aria-label]="'toggle ' + node.name"
                class="accordion-button">
          <mat-icon class="mat-icon-rtl-mirror">
            {{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
          </mat-icon>
          {{node.name}}
        </button>
      </div>
      <ul [class.example-tree-invisible]="!treeControl.isExpanded(node)">
        <ng-container matTreeNodeOutlet></ng-container>
      </ul>
    </li>
  </mat-nested-tree-node>
</mat-tree>

.ts

interface WorkingTreeNode {
  name: string;
  id: string;
  // node: ProjectModel | DatasetModel | RiskAssessmentModel;
  children?: WorkingTreeNode[];
}

@Component({
  selector: 'app-working-tree',
  templateUrl: './working-tree.component.html',
  styleUrls: ['./working-tree.component.css']
})
export class WorkingTreeComponent implements OnInit{

  treeControl = new NestedTreeControl<WorkingTreeNode>(node => node.children);
  dataSource = new MatTreeNestedDataSource<WorkingTreeNode>();
  workingTree: WorkingTreeNode[] = [];

  userId!: string;

  constructor(private projectService: ProjectService,
    private datasetService: DatasetService,
    private riskAssessmentService: RiskAssessmentService,
    private tokenStorageService: TokenStorageService ) {

  }

  ngOnInit() {
    this.userId = this.tokenStorageService.getUser().id;
    this.buildWorkingTree(this.userId);
    console.log("workingTree", this.workingTree);
    this.dataSource.data = this.workingTree;
    console.log("dataSource", this.dataSource);
  }

  public buildWorkingTree(userId: String): void {
    this.projectService.getUserProjects(userId).subscribe(
      (response: ProjectModel[]) => {
        response.forEach(project => {
          const projectChildren : WorkingTreeNode[] = [];
          this.datasetService.getProjectDatasets(project.id).subscribe(
            (response: DatasetModel[]) => {
              response.forEach(dataset => {
                const datasetChildren : WorkingTreeNode[] = [];
                this.riskAssessmentService.getDatasetRiskAssessments(dataset.id).subscribe(
                  (response: RiskAssessmentModel[]) => {
                    response.forEach(riskAssessment => {
                      datasetChildren.push({name: riskAssessment.name, 
                                            id: riskAssessment.id,
                                            children:[]})
                    })
                  },
                  (error: HttpErrorResponse) => {
                    alert(error.message);
                  }
                )
                projectChildren.push({name: dataset.name, 
                                      id: dataset.id,
                                      children:datasetChildren})
              })
            },
            (error: HttpErrorResponse) => {
              alert(error.message);
            }
          )
          this.workingTree.push({name: project.name, 
                                 id: project.id,
                                 children: projectChildren})
      })
        console.log("getUserProjects HttpResponse workingTRee", this.workingTree);
      },
      (error: HttpErrorResponse) => {
        alert(error.message);
      }
    )
  }

  hasChild = (_: number, node: WorkingTreeNode) => {
    !!node.children && node.children.length > 0;
    console.log("hasAChild", !!node.children && node.children.length > 0)
  }
}

I have attempted moving what's in my onInit method into the constructor, but unfortunately, it hasn't made any difference. I believe I'm following the example correctly, yet nothing is displaying and no errors are appearing in either my console or command prompt. Thank you for any assistance you can provide!

Answer â„–1

The function buildWorkingTree you're running is async, meaning that the line

this.dataSource.data = this.workingTree;
gets executed before the actual population of workingTree.

You can confirm this by adding a console.log next to this.workingTree.push to see the sequence of messages printed.

Make sure to set the data for the datasource only after it's fully populated. This may involve restructuring nested subscriptions into a single observable generating the final result, but that's a separate topic - focus on understanding async issues first and ask a new question if needed.

Answer â„–2

When using ForEach loops, keep in mind that they are asynchronous, which means the next sentence outside the loop may be executed before the loop finishes. One quick fix is to utilize the async/await keywords (I can help convert your code for this). Another approach suggested by @TotallyNewb is to transform the buildWorkingTree method into a single subscription. Here's a potential correction:

interface WorkingTreeNode {
    name: string;
    id: string;
    // node: ProjectModel | DatasetModel | RiskAssessmentModel;
    children?: WorkingTreeNode[];
  }
  
  @Component({
    selector: 'app-working-tree',
    templateUrl: './working-tree.component.html',
    styleUrls: ['./working-tree.component.css']
  })
  export class WorkingTreeComponent implements OnInit{
  
    treeControl = new NestedTreeControl<WorkingTreeNode>(node => node.children);
    dataSource = new MatTreeNestedDataSource<WorkingTreeNode>();
    workingTree: WorkingTreeNode[] = [];
  
    userId!: string;
  
    constructor(private projectService: ProjectService,
      private datasetService: DatasetService,
      private riskAssessmentService: RiskAssessmentService,
      private tokenStorageService: TokenStorageService ) {
  
    }
  
    async ngOnInit() {
      this.userId = this.tokenStorageService.getUser().id;
      await this.buildWorkingTree(this.userId);
      console.log("workingTree", this.workingTree);
      this.dataSource.data = this.workingTree;
      console.log("dataSource", this.dataSource);
    }
  
     async  buildWorkingTree(userId: String): Promise<void>{
      await this.projectService.getUserProjects(userId).subscribe(
        async( response: ProjectModel[]) => {
          await Promise.all(response.map(project => {
            const projectChildren : WorkingTreeNode[] = [];
            this.datasetService.getProjectDatasets(project.id).subscribe(
              async (response: DatasetModel[]) => {
                await Promise.all(response.map(dataset => {
                  const datasetChildren : WorkingTreeNode[] = [];
                  this.riskAssessmentService.getDatasetRiskAssessments(dataset.id).subscribe(
                    async (response: RiskAssessmentModel[]) => {
                      await Promise.all(response.map(riskAssessment => {
                        datasetChildren.push({name: riskAssessment.name, 
                                              id: riskAssessment.id,
                                              children:[]})
                      }));
                    },
                    (error: HttpErrorResponse) => {
                      alert(error.message);
                    }
                  )
                  projectChildren.push({name: dataset.name, 
                                        id: dataset.id,
                                        children:datasetChildren})
                }))
              },
              (error: HttpErrorResponse) => {
                alert(error.message);
              }
            )
            this.workingTree.push({name: project.name, 
                                   id: project.id,
                                   children: projectChildren})
        }));
          console.log("getUserProjects HttpResponse workingTRee", this.workingTree);
        },
        (error: HttpErrorResponse) => {
          alert(error.message);
        }
      )
    }
  
    hasChild = (_: number, node: WorkingTreeNode) => {
      !!node.children && node.children.length > 0;
      console.log("hasAChild", !!node.children && node.children.length > 0)
    }
  }

`

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How can I dynamically insert YouTube videos into my modal using Angular?

I'm currently working on a movie website and focusing on the trailers section. I've managed to retrieve 3 video files from the YouTube API, display their thumbnails, and set their id attribute to match the video ID on youtube.com. However, when ...

Tips for testing a component that utilizes an observable service

Seeking guidance on unit testing a function that is listening to an observable service. Feeling a bit lost on where to begin. Below is the component function I want to write unit tests for: register() { this._registrationService.registerUser(this.f ...

When quickly swiping, the button may not respond to the initial click

I'm currently developing an angular application that features a responsive navigation using the angular navbar. To ensure smooth navigation, I have implemented swipe gestures by utilizing hammer.js. Swiping right reveals the menu, while swiping left h ...

What is the reason for navigator.credentials.create returning Credential instead of PublicKeyCredential in TypeScript?

I am currently using typescript version 5.6 ("typescript": "~5.6") While trying to implement WebAuth, I noticed that the navigator.credentials.create method returns a Promise<Credential | null> https://i.sstatic.net/TMjU65xJ.png https://i.sstatic.n ...

Separating extraRoutes into their own file for various configurations in Scully causes an ImportError to occur

I am faced with a situation where I have multiple configurations in my Scully setup: scully.en.config.ts scully.de.config.ts All these configs share a common field called extraRoutes. I would like to separate this common field and store it in a new file n ...

AADSTS9002326: Token redemption across origins is only allowed for the client type of 'Single-Page Application'. Origin of request: 'capacitor://localhost'

My Ionic application is having trouble authenticating in Azure. I followed the guidance from a stackoverflow thread: Ionic and MSAL Authentication Everything went smoothly except for iOS, where I encountered the following error: AADSTS9002326: Cross ...

Prevent modal from closing when clicking outside in React

I am currently working with a modal component in react-bootstrap. Below is the code I used for importing the necessary modules. import React from "react"; import Modal from "react-bootstrap/Modal"; import ModalBody from "react-bootstrap/ModalBody"; impor ...

Can I integrate @types/pkg into my custom library to automatically have types included?

Imagine I am creating a library that utilizes types from the Definitely Typed repository (such as @types/pkg). Would it be feasible for my package to automatically include these types when installed, eliminating the need for consumers to separately instal ...

The category 'Moment' cannot be assigned to the category 'Date'. The characteristic 'toDateString' is not present in the category 'Moment'

I recently integrated moment into my Angular2 application, and encountered an issue when attempting to assign the date of this week's Saturday to a variable of type date, case "weekend": this.fromDate = moment().startOf('week ...

Guide on incorporating a bootstrap button into a React component using typescript

I am looking to create a custom React component using Typescript that wraps around a Bootstrap button. This component should allow me to pass parameters such as width, height, color, etc. These parameters would then be used to modify the appearance of the ...

Javascript Library Issue: "Implicitly Declared Type 'Any' Error"

I am currently in the process of developing a JavaScript library that will interact with an API. My goal is to create a module that can be easily published on npm and utilized across various frameworks such as Angular or React. Below is the code snippet fo ...

Exploring Functions in Object Literal Notation in TypeScript: Why is the Context of 'this' Assigned as Type 'any'?

It appears that the question has been posed before, but my search yielded no results. The issue at hand seems rather straightforward. TypeScript integrates well with object literal notation, however, when methods are defined within, it struggles to handle ...

Setting up Typescript: The Guide to Declaring a Dependent Property

In my current project, I am working on creating a declaration file for the quadstore library. This library has a class that requires a constructor parameter called contextKey. The value of this parameter determines the name of a field on method arguments. ...

Having trouble grasping this concept in Typescript? Simply use `{onNext}` to call `this._subscribe` method

After reading an article about observables, I came across some code that puzzled me. I am struggling to comprehend the following lines -> return this._subscribe({ onNext: onNext, onError: onError || (() => {}), ...

What is the reason for this JSON attribute showing up as undefined in the logs?

Recently, I've set up a nodejs lambda function that is triggered by an SQS queue connected to an SNS topic. Here's a snippet of the lambda code: 'use strict'; import { Handler } from 'aws-lambda'; const myLambda: Handler = ...

const error = new TypeError(`${calculateRelativePath(cwd, fileName)}: Skipping emission of file`);

Hey there! I have a typescript code snippet that looks like this: import { getConnection } from "typeorm"; import { GraphQLClient } from "graphql-request"; import got from "got"; import database from "./utils/database&quo ...

The function is trying to access a property that has not been defined, resulting in

Here is a sample code that illustrates the concept I'm working on. Click here to run this code. An error occurred: "Cannot read property 'myValue' of undefined" class Foo { myValue = 'test123'; boo: Boo; constructor(b ...

Testing Angular Components: Accessing HTML Elements by Data-QA Tag Attribute

Currently, I am in the process of writing unit tests for Angular and looking to retrieve the value of a custom textbox using the data-qa attribute. You can refer to this article for more information: https://dev.to/chriszie/data-qa-attribute-a-better-way-t ...

Decipher the splitButton tag from PrimeNG

I am currently attempting to translate items from "p-splitButton", but I am facing a challenge because the "items" is an object. How can I accomplish this task? [model]="items | translate" app.component.html <p-splitButton label="Save" icon="pi pi- ...

Having trouble with updating a Firebase database object using snap.val()

I'm trying to figure out how to update a property within the snap.val() in order to make changes to my Firebase database. function updateListItem(listItem) { var dbRef = firebase.database() .ref() .child('userdb') .child($sco ...