3

I am currently in the process of trying to build out a hierarchy view of users. My end goal is to generate the view using this hierarchy view or something similar.

The difficulty lies in how the JSON objects used to generate the hierarchy are given. This is a sample response (this response can be much bigger), where pid is the parent id and depth is the distance from the first parent.

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 explain the above response better, here is the visual hierarchy view of it: image

A lot of the answers and solutions I've seen so far utilize JSON with children nested in each one. Is it possible to generate the view using the json model above?

Any help or insight would be much appreciated! Thanks!

2
  • Well. Am I correct that you need to get a diagram (graph) from your data? Commented May 28, 2019 at 18:05
  • Yes, I need to get a diagram. Like the one, I linked in the first paragraph. Commented May 28, 2019 at 18:16

2 Answers 2

7

First, you need to convert your self-reference table to a hierarchical table (tree). I suggest you use a custom pipe to do this since you will be able to reuse this pipe in other places.

You can use Reactgular's code, my code from the StackOverflow thread, or write your own code. I create my converter pipe with 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);
  }
}

Don't forget to add it to the declaration section of your module:

app.module.ts

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

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

Now, you can create your component template and use the approach from the Hierarchy View CodePen. Since you need different markup for branches and leaves, it's handy to use NgTemplateOutlet's and NgIf structural directives. This's a good idea to move the level markup in a template and reuse it when you need to render a tree in Angular. The same idea is illustrated in my answer. Based on the provided CodePen code, your Angular markup may look as the following:

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>

Here, response is your original array:

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 forget to use the CodePen SASS styles in your project.

After this, you will a graph like:

graph

This is a StackBlitz project that demonstrates this approach in action.

Sign up to request clarification or add additional context in comments.

Comments

3

You can use a reducer to convert the flat array into UID map of nodes, and once you have the map you can populate the children easily. You can then just pick out the root node and use that to render the HTML.

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 render the tree by using the same component recursively, and have the template render 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;
}

It's not hard to modify the above to match the HTML/CSS you linked to in your question.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.