0

I have an somewhat complex object that includes nested objects as follows

      "data": {
        "John": {
          "title": "John",
          "value": "john"
        },
        "Ben": {
          "title": "Ben",
          "value": "ben"
        },
        "Workers": {
          "title": "Working Data",
          "startData": {
            "title": "Start Date",
            "value": "Mon, 27 Nov 2017 16:57:56 GMT"
          },
          "isPermanant": {
            "title": "Is Permanant",
            "value": "True"
          }
        },
        "Family": {
          "title": "Family Data",
          "familyMembers": {
            "title": "Family Members",
            "value": "4"
          },
          "pets": {
            "title": "Pets",
            "value": "2"
          }
        },
        "education": {
          "title": "Education Details",
          "degree": {
            "title": "Degree",
            "value": "Yes"
          },
          "graduated": {
            "title": "Graduated Year",
            "value": "2015"
          }
        }

Expected outcome is something like this

    <p>John <span>john</span><p>
    <p>Ben <span>ben</span><p>

<p>Working Data<p>
    <p>Start Date <span>Mon, 27 Nov 2017 16:57:56 GMT</span><p>
    <p>Is Permanant <span>True</span><p>

<p>Family Data<p>
    <p>Family Members <span>4</span><p>
    <p>Pets <span>2</span><p>

<p>Education Details<p>
    <p>Degree <span>Yes</span><p>
    <p>Graduated Year<span>2015</span><p>

I created a component that using a recursive way of displaying data

import { Component, Input, OnInit } from '@angular/core'

@Component({
    selector: 'timeline-data',
    template: 'timeline-data.html' 
})

export class TimelineDataComponent implements OnInit {
    @Input('data') data: any[];

    ngOnInit() {}
}

timeline-data.html as follows

<ng-container *ngIf="data.length">
     <ng-container *ngFor="let item of data">
          <ng-container *ngIf="item.value">
               <p>{{ item.title }} <span>{{ item.value }}</span></p>
          </ng-container>
          <ng-container *ngIf="!item.value">
               <timeline-data [data]="[item]"></timeline-data>
          </ng-container>
      <ng-container>
<ng-container>

But when I run this angular give me a RangeError: Maximum call stack size exceeded

What am I doing wrong here? How should I show this? Thanks in advance.

4
  • Your data isn't an array, it an object of objects. Is what you have included here correct? Commented Feb 4, 2018 at 17:55
  • @R.Richards yes. it's my bad I have called it an array. I fixed it. Commented Feb 4, 2018 at 17:57
  • Thanks for clarifying that. You are trying to loop through that object as if it were an array with your *ngFor. Assuming you have parsed the JSON into an object somewhere in the code, you would want to iterate through the keys of the object instead, right? Just trying to understand your goal. Commented Feb 4, 2018 at 18:03
  • @R.Richards this json will be coming from a remote server and content could be change, but the format is this. I added expected outcome to get more understand about my goal. Commented Feb 4, 2018 at 18:36

1 Answer 1

1

Based on your sample html, it will be difficult to do recursive rendering and not end up with nested <p> tags.

I took a slightly different approach and used an unordered list with an extra conditional to eliminate the empty li from the first iteration. If someone has a better way to do this, I'm all ears :)

I broke the rendering up into two main components:

tree.component.ts

import { Component, Input } from '@angular/core';

@Component({
  selector: 'tree-component, [tree-component]',
  template: `
    // omit the <li> wrapper for the "parent" iteration

    <ng-container *ngIf="parent">
      <tree-item [data]="data"></tree-item>
    </ng-container>

    <li *ngIf="!parent">
      <tree-item [data]="data"></tree-item>
    </li>
  `
})
export class TreeComponent {

  @Input()
  data: object;

  @Input()
  parent = false;

}

tree-item.component.ts

import { Component, Input } from '@angular/core';

@Component({
  selector: 'tree-item',
  template: `
    // iterate over the keys for each data item

    <ng-container *ngFor="let key of data | keys; let i = index;">

      // if the value is not an object, output the values
      // I assumed there would only be two values to wrap the second
      // value in a <span> according to your sample

      <ng-container *ngIf="!isObject(data[key])">
        <ng-container *ngIf="i === 0">{{ data[key] }}</ng-container>
        <span *ngIf="i === 1">{{ data[key] }}</span>
      </ng-container>

      // if the value is an object, recursively render the tree-component

      <ul tree-component *ngIf="isObject(data[key])" [data]="data[key]"></ul>
    </ng-container>
  `
})
export class TreeItemComponent {

  @Input()
  data: object;

  isObject(value: any): boolean {
    return value instanceof Object && value.constructor === Object;
  }

}

and a pipe utility to get the keys for each object, keys.pipe.ts:

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

@Pipe({
  name: 'keys'
})
export class KeysPipe implements PipeTransform {
  transform(value, args: string[]): any {
    return Object.keys(value);
  }
}

Give your data, and an implementation of:

<tree-component [data]="data" [parent]="true"></tree-component>

You end up with the result of this Plunkr: https://plnkr.co/edit/9DiCymkBDUNJSCFObV2G

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

1 Comment

This works pretty well. Thank very much for your work and time.

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.