Continuously apply the template in a recursive manner in Angular 2 without reintroducing any duplicated components

Recently, I delved into the world of angular 2 and found it to be quite fascinating. However, I'm currently facing a roadblock and could really use some assistance.

The scenario is as follows:

I am working on creating a select box with checkboxes in a hierarchical structure.

A
|---A1
|---A2
     |---A2.1
     |---A2.2
           |.....
|.....

The data structure will be returned by a web service in JSON format, so the number of hierarchical children is unknown upfront.

Here's what I've done so far:

@Component({
  selector: 'tree-view',
  template: `<ul>
    <li *ngFor="let items of data">
      <a href="javascript:void(0);"
      (click)="toggleMultiSelect($event,[items])">
        <input type="checkbox" name="{{items.name}}"
        [checked]="getChecked(items.id)"
        id="{{items.id}}">
        <label [attr.for]="items.id" class="custom-unchecked">{{items.name}}</label>
      </a>
      <tree-view
        *ngIf="items.children && items.children.size > 0"
        (isSelected)="resultChild($event)"
        [data]="items.children"></tree-view>
    </li>
  </ul>`,
  directives: [ MultiHierarchyTreeViewComponent ]
})

Currently, I repeat the selector when there are child elements in the data, resulting in a hierarchical structure for the select box. However, this approach makes select all, deselect all, parent-child selection operations challenging due to the repetition of the class.

I initially thought using <template> would resolve the issue, but unfortunately, it did not.

I've been trying to solve this puzzle for the past couple of days, but every attempt seems to lead to a dead end.

Answer №1

I recently encountered a similar situation and found that separating the tree data structure from its components was the most effective approach for me.

Tree data structures can be represented as nested objects:

export interface TreeNodeInterface {
    parent: TreeNodeInterface;
    children: TreeNodeInterface[];
}

A basic class implementing this interface might look like this (full code available here):

export class TextTreeNode implements TreeNodeInterface {
    private parentNode: TreeNodeInterface;
    private childrenNodes: TreeNodeInterface[] = [];

    constructor(text: string, options: TreeNodeOptions|Object, children: TreeNodeInterface|TreeNodeInterface[] = []) {
        // ...
    }

    get parent() {
        return this.parentNode;
    }

    get children() {
        return this.childrenNodes;
    }
    // ...
}

The rendering of the tree is then handled by a separate component.

I chose this approach because it allows me to easily show/hide subtrees using *ngIf, keeping the DOM relatively simple even with large trees (assuming only a portion of the tree is usually visible). It also enables reusability of the tree in different parts of my application.

Although you could achieve similar functionality with QueryList, separating the tree structure worked better for my specific use case.

@Component({
    selector: 'ng2-treeview',
    directives: [ TreeViewComponent ],
    template: `
        ...
        <ng2-treeview *ngFor="let child of node.children" [node]="child"></ng2-treeview>
    `
})
export class TreeViewComponent implements TreeViewInterface, AfterViewInit
{
    @Input() node: TreeNodeInterface;        
    // ...
}

Using the component is straightforward:

@Component({
    selector: 'demo',
    directives: [TreeViewComponent],
    template: `
        <ng2-treeview [node]="textTreeView"></ms-treeview>
    `
})
export class DemoComponent {
    textTreeView = new TextTreeNode('Root node', null, [
        new TextTreeNode('Child node #1'),
        new TextTreeNode('Child node #2'),
        new TextTreeNode('Child node #3'),
        new TextTreeNode('Child node #4', null, [
            new TextTreeNode('Hello'),
            new TextTreeNode('Ahoy'),
            new TextTreeNode('Hola'),
        ]),
        new TextTreeNode('Child node #5'),
    ]);
}

You can find the complete source code on ng2-treeview. Please note that the documentation is not yet complete and is based on the old Angular 2 RC.1 version.

Edit: If you need to handle events like mouse clicks on each tree node, you can create a service injected into TreeViewComponent:

import {EventEmitter, Injectable} from '@angular/core';

@Injectable()
export class TreeNodeService {
    click: EventEmitter<Object> = new EventEmitter();
}

Then, each (click) event on the TreeViewComponent can be passed to the service:

nodeClick(event) {
    this.treeNodeService.click.emit(this.node.id);
}

Lastly, DemoComponent can receive TreeNodeService as a dependency in its constructor and subscribe to the click event emitter. Multiple independent instances of TreeNodeService can coexist since DemoComponent can also provide one.

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

Transferring data from a child component to a parent component in Angular using @ViewChild requires providing 2 arguments

Currently, I am attempting to transmit data using @Output & EventEmitter and @ViewChild & AfterViewInit from a child component to a parent component. Below is the code from my parent component .html file: <app-child (filterEvent)=" getValu ...

Steps for integrating custom slot properties in MUI data grids

Custom pagination has been successfully implemented using the mui datagrid component. However, when attempting to pass props for pagination using datagrid's slotProps, an issue arises stating that the type of onChange does not match. How can this be c ...

What is the essential Angular 2 script that must be included for a simple Angular 2 application to function properly?

I'm currently working through the latest Tuts+ tutorial on Angular 2 In the tutorial, the author references adding this script: <script src="node_modules/angular2/bundles/angular2.sfx.dev.js"></script> However, in the most recent beta re ...

Creating a build task in Visual Studio Code with universal TypeScript compiler settings

Our project has the following structure: +-- views +-- viewXXX +-- ts ¦ +-- controller.ts ¦ +-- helper.ts ¦ +-- ... (*.ts) +-- viewXXX.ctrl.js // this is the desired output file +-- viewXXX.c ...

What is the best way to remove a specific row from an Angular Material table that does not have any filters

Here is my samplepage.component.ts code: import { Component } from '@angular/core'; @Component({ selector: 'app-batchticketvalidation', templateUrl: './batchticketvalidation.component.html', styleUrls: ['./batchtic ...

Guide on initializing a Redux toolkit state with an array of objects or local storage using TypeScript

Currently, I am attempting to set an initial state (items) to an array of objects or retrieve the same from localStorage. However, I am encountering the following error. Type 'number' is not assignable to type '{ id: string; price: number; ...

Executing multiple http post requests in Angular2 using a for loop

I've encountered an issue while attempting to upload multiple files with individual titles. The problem arises when sending requests to the server, as I'm trying to pass each file and its corresponding title one by one. I have an array called bin ...

"Encountering a problem during the installation of the Angular

After investing numerous hours, I am still unable to identify the issue with running an angular based project. node version: v12.16.1 I executed npm install -g @angular/[email protected] However, upon entering the command ng build --prod, I enco ...

The TypeScript generated definition file (.d.ts) is failing to work properly in conjunction with the typings specified in package.json

I've successfully created a definition file (d.ts) for my TypeScript project using the --declaration argument with the tsc compiler. However, when I attempt to publish the package with the typings property in the npm package.json, this generated defi ...

Creating a seating arrangement for a movie theater screen

Need help creating a seating layout based on user input. When the user enters row number 1 and 12 seats, I want to generate 12 divs in one row. If the user enters row number 2 and 13 seats, then the next row should have 13 divs. import { Seats } from &ap ...

Tips for Developing Drag Attribute Directive in Angular 2.0

Currently, I am referencing the Angular documentation to create an attribute directive for drag functionality. However, it seems that the ondrag event is not functioning as expected. Interestingly, the mouseenter and mouseleave events are working fine ac ...

Angular iframe is not displaying content

My component includes an iframe as shown below: <iframe [innerHTML]="html"></iframe> When I use: <div [innerHTML]="html"></div> it works perfectly. However, it must be contained within an iframe to apply custom styles and script ...

There is no matching signature for Type when using withStyles

Struggling to convert my React App to typescript, I keep encountering the error below and cannot decipher its meaning. The app functions perfectly in plain JS. My package version is material-ui@next TS2345: Argument of type 'typeof ApplicationMenu&a ...

Tips for troubleshooting JSON sorting in Angular

I am currently troubleshooting column positions within my application and need to inspect the sorted column definition. After retrieving the column definition from my API, I proceed to sort them. However, I also want to log the sorted list/Array to my co ...

Leverage the power of NPM Libraries in Angular using Angular-CLI

I am attempting to integrate the npm library progressbar.js into my Angular 2 application built using Angular-CLI. After installing it with npm install progressbar.js --save, I encountered an issue as the documentation suggests using var ProgressBar = req ...

Having trouble successfully deploying the app generated by "dotnet new angular" onto Azure

After creating an app using the dotnet new angular template and running dotnet run, everything seemed to be working smoothly. However, when I pushed the code to GitHub and set up continuous deployment in Azure, the build process failed. I turned on Devel ...

Is there a resource or extension available for identifying design flaws in Typescript code?

Currently, I am in the midst of an Angular project and am eager to identify any design flaws in my Typescript code. Are there any tools or extensions available that can help me pinpoint these design issues within my project? Any assistance would be greatl ...

What steps are involved in adding a table footer to ng2-smart-table?

I'm currently incorporating ng2-smart-table into my application. I am looking to include a table footer below the table to provide additional information such as Sum Amount, Discount Amount, and more. Below is an example of my code in a TypeScript fi ...

Compile a roster of service providers specializing in unit testing imports

Recently joining a new team that works with Angular, they asked me to implement unit testing on an existing project built with Angular 8. After researching the best approach, I decided to use Karma + Jasmine for testing. I set up a .spect.ts file structure ...

What causes the appearance of the "?" symbol at the beginning of the URL and triggers a reboot of the app when moving through the absolute path?

I am facing an issue. In my multi-module application with lazy loading, I encountered a strange behavior when trying to navigate between child lazy modules. Transition from top module to bottom child module works fine, but routing from one bottom child la ...