Getting node siblings within an Angular Material nested tree: A comprehensive guide

Struggling to retrieve the list of sibling nodes for a specific Angular Material tree node within a nested tree structure.

After exploring the Angular Material official documentation, particularly experimenting with the "Tree with nested nodes," I found that neither the NestedTreeControl nor the SelectionModel available through @angular/cdk/collections offer a direct way to access or visualize the sibling nodes of a given node. It's also challenging to determine the level of the NESTED tree where a node is located unless using the flat tree implementation.

Below is the HTML snippet:

<mat-tree [dataSource]="dataSource" [treeControl]="treeControl" class="example-tree">
  <!-- Tree node template for leaf nodes -->
  <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
    <li class="mat-tree-node">
      <button mat-icon-button disabled></button>
      {{node.name}}
    </li>
  </mat-tree-node>
  <!-- Tree node template for expandable nodes -->
  <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">
          <mat-icon class="mat-icon-rtl-mirror">
            {{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
          </mat-icon>
        </button>
        {{node.name}}
      </div>
      <ul [class.example-tree-invisible]="!treeControl.isExpanded(node)">
        <ng-container matTreeNodeOutlet></ng-container>
      </ul>
    </li>
  </mat-nested-tree-node>
</mat-tree>

Here is the accompanying class code:

import {NestedTreeControl} from '@angular/cdk/tree';
import {Component} from '@angular/core';
import {MatTreeNestedDataSource} from '@angular/material/tree';

// Food data structure with nesting
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: 'Brussel sprouts'},
        ]
      }, {
        name: 'Orange',
        children: [
          {name: 'Pumpkins'},
          {name: 'Carrots'},
        ]
      },
    ]
  },
];

// Component definition
@Component({
  selector: 'tree-nested-overview-example',
  templateUrl: 'tree-nested-overview-example.html',
  styleUrls: ['tree-nested-overview-example.css'],
})
export class TreeNestedOverviewExample {
  // Creating tree control and data source
  treeControl = new NestedTreeControl<FoodNode>(node => node.children);
  dataSource = new MatTreeNestedDataSource<FoodNode>();

  constructor() {
    this.dataSource.data = TREE_DATA;
  }

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

And some CSS styling:

.example-tree-invisible {
  display: none;
}

.example-tree ul,
.example-tree li {
  margin-top: 0;
  margin-bottom: 0;
  list-style-type: none;
}

The main challenge lies in accessing the sibling nodes within the NESTED tree structure and determining the exact level based on the current node being processed.

Answer №1

If you're seeking a solution to a similar issue, here's how I tackled my problem.

  1. To start, establish custom data attributes for the node class. Here's what my node class looks like now:

import {NestedTreeControl} from '@angular/cdk/tree';
import {Component} from '@angular/core';
import {MatTreeNestedDataSource} from '@angular/material/tree';

/**
 * Food data with nested structure.
 * Each node has a name and an optional list of children.
 */
interface FoodNode {
  name: string;
  children?: FoodNode[];
}

export class FoodNode {
  children: BehaviorSubject<FoodNode[]>;
  constructor(
    public name: string,
    public type: string,
    public id: number,
    children?: FoodNode[],
    public parent?: FoodNode,
    public level = 0
  ) {
    this.children = new BehaviorSubject(children === undefined ? [] : children);
  }
}

  1. Next, here is the code for the nested tree class.
import { NestedTreeControl } from '@angular/cdk/tree';

export class NestedTreeComponent implements OnInit, OnDestroy {
  // TREE CONTROLS
  treeControl: NestedTreeControl<FoodNode>;
  foodTreeLevels = [];
  treeDepth = 1; // initialize tree with default depth 1

  ngOnInit() {
   // initialize tree controls and data source
   this.treeControl = new NestedTreeControl<FoodNode>(this.getChildren);
  }

  /** Get node children */
  getChildren = (node: FoodNode): Observable<FoodNode[]> => node.children;

  /**
   * @desc Add the node to its parent's children object
   */
  addNewFood(parent: FoodNode, foodName: string, foodType: string, foodId: number) {
    // Add the food to the list of children
    const child = new FoodNode(foodName, foodType, foodId, [], parent, 0);
    child.level = parent.level + 1;
    // add a new level to the tree if the parent of this node has no children yet
    if (parent && parent.children.value.length === 0 && !this.foodTreeLevels.includes(child.level)) {
      this.foodTreeLevels.push(child.level);
      this.treeDepth += 1;
    }
    // update the parents' children with this newly added node if the parent exists
    if (parent) {
      const children = parent.children.value;
      children.push(child);
      parent.children.next(children);
   }

  }
  1. Now, to access the siblings of any node in the tree, simply go to the node's parent and retrieve all its children like so:
// This is a node of type FoodNode
const parent = theNodeINeedToGetSiblingsFor.parent; 
const siblings = parent.children.value;

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

Combining default and named exports in Rollup configuration

Currently, I am in the process of developing a Bluetooth library for Node.js which will be utilizing TypeScript and Rollup. My goal is to allow users to import components from my library in various ways. import Sblendid from "@sblendid/sblendid"; import S ...

Observing changes in the DOM using Angular

Is there a way to programmatically track the changes of a DOM node? It can be time-consuming to detect the delta in the inspector, especially with angular components that have many class names. https://i.stack.imgur.com/ns6rR.png I am looking for a more ...

Unraveling nested elements with the array map() method in Angular2 and Typescript: Fixing the issue of undefined property reference while mapping

Hey there! I'm currently working with Angular 4 and I have a piece of code that parses data from an API into a TypeScript array of rows. It's important to note that the code functions properly if elements like 'item.tceCampRun' and &apo ...

A guide on implementing a "Select All" trigger in mat-select with Angular8/Material

Here is the code I have created: <mat-form-field appearance="outline"> <mat-label>Handler Type</mat-label> <mat-select multiple [(value)]="handlerType"> <mat-option *ngFor="let handler of handlerT ...

Is it possible to use string indexes with jQuery each method in Typescript?

When running a jQuery loop in Typescript, I encountered an issue where the index was being reported as a string. $.each(locations, (index, marker) => { if(this.options && this.options.bounds_marker_limit) { if(index <= (this.opt ...

Showing container element only if certain condition is met but display the children in Angular 2+

I am looking to create a grid-style view for a list of Angular components, where I can display up to 4 components in each row. The question that comes close to what I need is the following: Angular 2 NgIf, dont render container element on condition but sh ...

Exploring a JSON object using PlaywrightWould you like to know how

Greetings! Here is a snippet of code that I have, which initiates an API call to a specific URL. const [response] = await Promise.all([ page.waitForResponse(res => res.status() ==200 && res.url() == & ...

When I attempt to add a todo item by clicking, the Url value is displayed as "undefined"

I am facing an issue with my household app where, upon clicking the button to navigate to the addtodo page, the URL specific to the user's house is getting lost. This results in the todolist being stored as undefined on Firebase instead of under the c ...

Angular 2 smart table row indexing

My Angular project is using ng2-smart-table and it's functioning correctly. However, I am looking for a way to display row numbers in each row without having to set a property in the data itself. If anyone has a workaround or solution, please let me k ...

Displaying time in weekly view on the Angular 4.0 calendar

I've integrated the library into my Angular application to manage calendar events display and creation. The app features a monthly, weekly, and daily view option for users. However, I noticed that in the weekly view, only the dates are shown without ...

Tips on preventing the copying of .txt and .xml files with the fs-extra.copySync function

Currently, I am working on a small TypeScript assignment and facing an issue that I can't seem to solve. Any guidance or advice on the problem mentioned below would be greatly appreciated. The task at hand involves copying a directory from one locati ...

Is it possible to dynamically close the parent modal based on input from the child component?

As I follow a tutorial, I am working on importing the stripe function from two js files. The goal is to display my stripe payment in a modal. However, I am unsure how to close the modal once I receive a successful payment message in the child. Below are s ...

Encountering ReferenceError when attempting to declare a variable in TypeScript from an external file because it is not defined

Below is the typescript file in question: module someModule { declare var servicePort: string; export class someClass{ constructor(){ servicePort = servicePort || ""; //ERROR= 'ReferenceError: servicePort is not defined' } I also attempted t ...

What is the process of creating a callback in Angular using TypeScript?

Despite finding numerous resources, I am still struggling to fully grasp the concept at hand. The issue revolves around two functions in particular: roulette_animation(){ do animation (may take 5 sec) } alertResult(){ alert('You win') } My obje ...

How to check all checkboxes in Angular using ngFor and ngIf?

Is there a way to select all checkboxes in an Angular form that uses ngFor and ngIf? I want to activate all checkboxes for the months when I click on "Select All". The list of months is stored in an array. Click here to see the HTML representation of the ...

Communicating data between Angular components that have no direct binding or relationship

Struggling to transfer data between two unrelated components, anyone have advice on how to accomplish this? Here's an example: I have a page with 3 buttons that pass string values upon click to a variable named selectAgent. ~agents.html~ <div rou ...

Problem with Scroll Listener on Image in Angular 4: Window Scroll Functioning Properly

Hello, I am a newcomer who is currently working on creating a small application that allows users to zoom in on an image using the mouse scroll. <img (window:scroll)="onScroll($event) .....></img> The code above works well, however, it detec ...

React's memo and/or useCallback functions are not functioning as anticipated

Within my Home Component, there is a state called records, which I utilize to execute a records.map() and display individual RecordItem components within a table. function Home() { const [records, setRecords] = useState<Array<RecordType>>(l ...

Mental stability groq fails to provide the requested information

Having difficulty using Groq to fetch data. All other Groq queries work fine, except this one. When manually setting the $slug variable, the data I'm trying to query works with the Sanity Groq VS plugin but returns undefined in my web app. Query: exp ...

Angular Forms testing with Karma Unit Testing is throwing the following error message: ""

Here is a test case scenario: fit('When the Address field is left blank, it should be marked as Invalid', () => { component.basicBookFormGroup.patchValue({ bookName: 'My Site Name', bookOrg: 'Org ...