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

Integrate a @Component from Angular 2 into the Document Object Model of another component

One of my components is called TestPage import { Component } from '@angular/core'; @Component({ selector: 'test-component', template: '<b>Content</b>', }) export class TestPage { constructor() {} } Another ...

Ionic Troubleshoot: Issue with Reading Property

Encountering an error: A TypeError occurs: Cannot Read Property of "username" of Undefined The HTML code responsible for the error is as follows: <ion-content padding style="text-align: center; margin-top: 35px"> <form (ngSubmit)="logFor ...

What are the steps for deploying an Angular 2 project to a server with PUTTY?

After developing an Angular 2 app with Angular-CLI on my local server, I have reached the production phase and now need to upload it to a CentOS server using Putty. I attempted to follow instructions from this source for installing node and npm on the ser ...

Exploring the dynamic world of Angular2 animations paired with the

In my layout, I have a row with three columns and two buttons to toggle the visibility of the side panels: col-md-3 col-md-7 col-md-2 ------------------------------------ | | | | | left | middle panel | | ...

Pending activation of the Timer Fired event

Below is some code I have for implementing swipe gesture functionality: this.topSlide = this.elementRef.nativeElement.querySelector('.product_rate_slide'); if (this.topSlide) { this.topSlide.addEventListener('touchstart', this.hand ...

Error: Incorrect Path for Dynamic Import

Recently, I've been trying to dynamically load locale files based on the locale code provided by Next.js. Unfortunately, every time I attempt a dynamic import, an error surfaces and it seems like the import path is incorrect: Unable to load translatio ...

Angular2 - Model not being refreshed by Directive

I have implemented a directive on an HTML input box to handle numeric values. The directive is meant to "clean" the number (removing commas, dollar signs, etc) when a user pastes a number into the textbox. Although the cleaning code works properly, the iss ...

What is the reason behind TypeScript requiring me to initialize a property even though I am retrieving its value from a local reference?

I am just beginning to explore Angular. This is the template for my custom component: <div class="row"> <div class="col-xs-12"> <form action=""> <div class="ro"> <d ...

Navigating the way: Directing all TypeScript transpiled files to the build folder

I am currently working on a project using Angular2/Typescript, and I have the tsconfig.js file below: { "compilerOptions": { "module": "commonjs", "moduleResolution": "node", "target": "es5", "sourceMap": true, ...

The CSS overflow scroller trims the excess background color

Attempting to build a website, I encountered an issue with displaying a scroll bar. Despite using the CSS property overflow: auto, I faced another problem. Let me illustrate the issue through a simple example. I have an outer div with the style overflow: ...

What is the best way to retrieve an object from a loop only once the data is fully prepared?

Hey, I'm just stepping into the world of async functions and I could use some help. My goal is to return an object called name_dates, but unfortunately when I check the console it's empty. Can you take a look at my code? Here's what I have ...

`The error "mockResolvedValue is not recognized as a function when using partial mocks in Jest with Typescript

Currently, I am attempting to partially mock a module and customize the return value for the mocked method in specific tests. An error is being thrown by Jest: The error message states: "mockedEDSM.getSystemValue.mockResolvedValue is not a function TypeEr ...

Adjusting the button's background hue depending on the table element

Can someone help me create a table like the one shown in this image I need to determine the button color based on the text value. How can I achieve this? <ng-container matColumnDef="status"> <th mat-header-cell *matHeaderCellDef&g ...

Angular 2 rc1 does not support ComponentInstruction and CanActivate

In the process of developing my Angular 2 application with Typescript using angular 2 rc.1, I've noticed that the official Angular 2 documentation has not been updated yet. I had references to ComponentInstruction Interface and CanActivate decorator ...

What is the best way to link together Angular observables?

In order for my component to make API requests, it needs to check if certain app preferences are set. Currently, I have implemented a method where the component's data is refreshed every 2 minutes using a timer: ngOnInit(): void { this.subscriptio ...

A recursive approach for constructing a tree structure in Angular

Currently, I am working on a project involving the implementation of crud functions. To display the data in a tree-like structure, I am utilizing the org chart component from the PrimeNg library. The data obtained from the backend is in the form of an arra ...

Updating Elements in an Array Using JavaScript is Not Functioning as Expected

In my Angular application, I have included some lines of TypeScript code which involve Boolean variables in the constructor and an array of objects. Each object in this array contains input variables. selftest: boolean; failed: boolean; locoStateItem ...

Enhance your Fastify routes by incorporating Swagger documentation along with specific tags and descriptions

Currently, I am utilizing fastify 3.28.0 in conjunction with the fastify-swagger plugin and typescript 4.6.2. My goal is to include tags, descriptions, and summaries for each route. As per the documentation found here, it should be possible to add descrip ...

When attempting to update Ionic2 from BETA11 to RC0, the error "bundle failed: 'InMemoryBackendService' is not exported" was encountered

An error occurred while bundling the application: 'InMemoryBackendService' is not exported by node_modules\angular2-in-memory-web-api\index.js (imported by src\app\app.module.ts). For assistance with resolving this ...

Having Trouble with React, TypeScript, and MUI Tabs? Dealing with Overload Errors?

Currently, I'm in the process of building a component using MUI Tabs. Here's a snippet of my code: <Box> <Tabs value={value} onChange={handleChange} variant='fullWidth'> {RoomTabs.map((tab, index) => ( ...