Creating a hierarchical visualization in Angular using a JSON object array

I'm currently working on creating a hierarchical view of users. My ultimate goal is to utilize this hierarchy view or something similar.

The challenge lies in how the JSON objects used to construct the hierarchy are structured. Here's an example response (which could be much larger), showing user ids, parent ids, depths, and parent status:

response = [
    {uid: "abc", pid: null, depth: 1, parent: true},
    {uid: "def", pid: "abc", depth: 2, parent: false},
    {uid: "ghi", pid: "abc", depth: 2, parent: true},
    {uid: "jkl", pid: "ghi", depth: 3, parent: false},
    {uid: "mno", pid: "ghi", depth: 3, parent: false},
]

To illustrate the response above, here's the visual representation of the hierarchy view: image

Many solutions I've come across involve JSON with nested children. Is it feasible to generate the view using the provided JSON structure?

Any assistance or insights on this matter would be greatly appreciated! Thank you!

Answer №1

To start, the first step is to transform your self-reference table into a hierarchical table (tree). I recommend utilizing a custom pipe for this task as it allows for reusability in various contexts.

You have options such as using Reactgular's code, my code from a previous StackOverflow thread, or crafting your own implementation. Personally, I crafted a converter pipe leveraging Reactgular's code:

converter.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'converter'
})
export class ConverterPipe implements PipeTransform {
  transform(array: any[], id: string = 'uid', parentId: string = 'pid'): any[] {
    const map = array.reduce(
      (acc, node) => ((node.items = []), (acc[node[id]] = node), acc),
      {}
    );

    return Object.values(map)
      .map(
        node => (node[parentId] && map[node[parentId]].items.push(node), node)
      )
      .filter(node => node[parentId] === null);
  }
}

Remember to include this pipe in the declaration section of your module:

app.module.ts

import { ConverterPipe } from './converter.pipe';

@NgModule({
  declarations: [
    ConverterPipe
  ]
})
export class AppModule { }

With this setup, you can proceed to create your component template following the example provided in a CodePen demonstration like Hierarchy View. For cases requiring distinct markup for branches and leaves, leverage NgTemplateOutlet and NgIf directives. Centralizing the tree rendering logic in a template enhances reusability as demonstrated in my referenced solution.

app.component.html

<div class="hv-wrapper">
  <ng-template #Item let-item>
    <ng-container *ngIf="!item.items.length; else Component">
      <p>{{ item.uid }}</p>
    </ng-container>
    <ng-template #Component>
      <div class="hv-item">
        <div class="hv-item-parent">
          <p>{{ item.uid }}</p>
        </div>
        <div class="hv-item-children">
          <div class="hv-item-child" *ngFor="let child of item.items">
            <ng-container
              *ngTemplateOutlet="Item; context: { $implicit: child }"
            ></ng-container>
          </div>
        </div>
      </div>
    </ng-template>
  </ng-template>

  <ng-container *ngFor="let child of response | converter"
    ><ng-container
      *ngTemplateOutlet="Item; context: { $implicit: child }"
    ></ng-container
  ></ng-container>
</div>

In this code snippet, use the original array response:

app.component.ts

export class AppComponent {
  response = [
    { uid: 'abc', pid: null, depth: 1, parent: true },
    { uid: 'def', pid: 'abc', depth: 2, parent: true },
    { uid: 'ghi', pid: 'abc', depth: 2, parent: false },
    { uid: 'jkl', pid: 'ghi', depth: 3, parent: false },
    { uid: 'mno', pid: 'ghi', depth: 3, parent: false }
  ];
}

Don't overlook the inclusion of the CodePen SASS styles into your project.

Upon completion, your generated graph will resemble the one outlined below:

https://i.sstatic.net/pKOC2.png

For a practical demonstration, refer to this StackBlitz project showcasing this approach in action.

Answer №2

To transform the flat array into a UID map of nodes, you can utilize a reducer. Once the map is created, populating the children becomes a straightforward task. Simply selecting the root node will enable you to render the HTML content with ease.

const map = [
   {uid: "abc", pid: null, depth: 1, parent: true},
   {uid: "def", pid: "abc", depth: 2, parent: true},
   {uid: "ghi", pid: "abc", depth: 2, parent: false},
   {uid: "jkl", pid: "ghi", depth: 3, parent: false},
   {uid: "mno", pid: "ghi", depth: 3, parent: false},
].reduce((acc, node) => (node.children = [], acc[node.uid] = node, acc), {});

const [root] =  Object.values(map)
                      .map(node => (node.pid && map[node.pid].children.push(node), node))
                      .filter(node => node.pid === null);
    
console.log(root);

You can display the tree structure by employing the same component recursively and adjusting the template to showcase the children.

@Component({
      selector: 'app-node',
      template: `
          <span>Node</span>
          <app-node [node]="child" *ngFor="let child of node.children"></app-node>
      `
})
export class NodeComponent {
    @Input()
    public node: any;
}

Adapting the provided solution to match the style from the linked HTML/CSS won't be a daunting task.

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

Incorporating a CSS stylesheet into the theme settings of the Stratus 2 Beta platform

I have been attempting to personalize my Stratus 2 Beta by implementing a custom theme. I created a separate CSS file named "stratus.css" and stored it in the CSS directory of my website - with the CSS code being identical to this example. Below is my Jav ...

Show Timing on the Y-Axis - Bubble Graph

Recently, I stumbled upon the Bubble Chart feature in ng2-charts. I am trying to display data based on time on the Y-axis and values on the X-axis. My dataset consists of x:[10,35,60], y:["7.00 AM"], with r having the same value as x. However, the sample d ...

Utilizing ellipses within a list item to create a visually appealing design

I've been struggling with this problem for hours, but I can't seem to find a solution. How can I ensure that the text in a list item respects the size of its parent container and uses an ellipsis when necessary? Here's the scenario: <?P ...

Creating an array of multiple divs based on numerical input

I am working on a project to show multiple divs based on the user's input number. For example, if the user selects 3, then 3 divs should be displayed. While I have successfully implemented this functionality, I need to dynamically assign IDs to each ...

The dividers flicker in and out of view

I have a menu with 7 elements, where clicking on an element causes its content to appear by fading in. If another element is clicked, the current content fades out and the new content fades in. I've successfully implemented this concept for 3 of the 7 ...

What is the most effective way to move specific data from one page to another in Angular/Typescript?

Welcome to my Main Page! https://i.stack.imgur.com/m9ASF.png This is where I want to start my journey. https://i.stack.imgur.com/E8pAW.png My goal is to click the Last 1 Day button to redirect to another page with the date filter and ItemId values already ...

Changing the position of an image can vary across different devices when using HTML5 Canvas

I am facing an issue with positioning a bomb image on a background city image in my project. The canvas width and height are set based on specific variables, which is causing the bomb image position to change on larger mobile screens or when zooming in. I ...

Error encountered: ExpressionChangedAfterItHasBeenCheckedError when trying to load the loading indicator

I encountered an issue with my loading indicator that I cannot seem to resolve: LoadingIndicatorComponent.html:2 ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'hidden: true&ap ...

What is the process for updating semi-structured data in Snowflake?

In one of the snowflake columns, we have a JSON object that stores a variable number of value/pairs, making the data semi-structured. What options do I have to update a specific value pair within the JSON object? Do I need to extract the entire JSON, con ...

Passing additional parameters to an Angular directive individually

Is there a way to pass two parameters separately to my directive instead of as one combined parameter? Currently, I am able to add the parameters as one parameter (*ovLoading="!isDataReceived;noBackground:true"), but I would prefer to have them as two sepa ...

Mobile version shows white spaces when using particles.js and bootstrap 4.6

This is how I implement particles.js: <div id="particles-js"></div> @RenderSection("Header", required: false) <main role="main" class="container"> @RenderBody() </main> <script> ...

TS6059 found in excluded folder

I'm facing an issue with my tsconfig.json file that looks like this: {"compilerOptions": { "module": "commonjs", ...

Tips for optimizing search functionality in Angular to prevent loading all data at once

An exploration for information within vast datasets is triggered by AngularJS when the input contains more than 3 characters. var app = angular.module('test_table', []); app.controller('main_control',function($scope, $http){ $scope ...

The absence of a Response Entity is puzzling, especially when the server returns an Error-404 JSON

I have been working with Spring REST to manage my requests. Below is a sample of my code: RestTemplate restTemplate = new RestTemplate(); HttpEntity entity = new HttpEntity(headers); ResponseEntity<String> responseEntity = null; try { ...

Instructions for changing the background color continuously

One of my templates includes the following input: <div class="form-group" LayoutDirective=""> <label>Background color for views</label> <input type="text" name="background_color" id="background_color" ng-model="selectedLayout. ...

Make the background disappear when the text field is left empty and the cursor is not present (onUnfocus)

When the text field is empty and there is no cursor in the text field, I want it to be transparent and for the spell checker not working. The result should be displayed a little to the left inside a <div>. Should this be done using CSS, JavaScript, ...

Guide on transforming application/atom+xml response to JSON using REST template

I am currently working with atom/xml data and need to convert it into a Java object. However, I'm encountering an error during the conversion process. Here is the code snippet: ResponseEntity<BusinessPartner> response = restTemplate.exchange( ...

Surprising behavior witnessed in Postgres query when using the results of an UPDATE within a Common Table Expression (CTE

WITH deleted_items AS ( SELECT name FROM items WHERE id = $1 ), updated_records AS ( UPDATE records r SET details = JSONB_SET(details, '{items}', (SELECT jsonb_agg(elem) ...

Angular 2: What is the reason for preventing the use of subscribe on the Subscriber object?

If I have an observable object o : let o: Observable<Object> = ... I am able to subscribe to this object, but why is it not permitted to subscribe to the Subscriber object? To illustrate with a real-life example: myServiceCall() { let o: O ...

Looking to extract and upload a thumbnail image from an HTML5 video element

I am trying to extract and upload a thumbnail from an HTML5 video tag, Below is my code snippet: var w = 135;//video.videoWidth * scaleFactor; var h = 101;//video.videoHeight * scaleFactor; var canvas = document.createElement('canvas&apo ...