0

Below is the array of list items,

  data = [
    {
      "type": "view-item",
      "depth": 0,
      "text": "View Item One",
    }
  ]

Note: - depth indicates the hierarchy level of the items.

I could able to display all the items.

How can this be done ? Please help.

stack s
  • 109
  • 13
  • 3
    This has little to do with Angular. You need to come up with an algorithm to transform your data into a nested list of elements and then use a recursive component to print it. It could be simplified if you know the maximum depth of nesting, though. – Lazar Ljubenović Apr 29 '19 at 06:00
  • I second @LazarLjubenović 's comment. Also.. You will have better luck tagging JavaScript or TypeScript on your question. – wentjun Apr 29 '19 at 06:02
  • its on `Typescript` and `depth` level can be Nth level – stack s Apr 29 '19 at 06:04
  • To come up with a solution , there has to be some sort of pattern , like the `ol` is supposed to be sub list of `ul`. like in this case the `ol` is the sub list for third element of `ul` – manish kumar Apr 29 '19 at 06:37
  • it's easy if your date becomes like {type:..,test:..children:[{type:..test:..}, else you always can use [styles.margin-left]="{{1*item.depth+'em'}} – Eliseo Apr 29 '19 at 06:52
  • @Eliseo Adding a margin won't magically change the list semantically correct, so no, you cannot “always” use it. In fact, you can _never_ use it. – Lazar Ljubenović Apr 29 '19 at 07:10

2 Answers2

1

Firstly you need to transform the flattened array into a nested array. Then you'll be able to recursively loop over it to create the structure you need.

Here's the solution for building a flattened array from your original structure. I dropped the type for simplicity, you'll need to maintain this as well in your application so you can later choose whether to use ol or ul.

interface FlatItem {
  depth: number
  text: string
}

interface Item {
  text: string
  children: Item[]
}

function unflatten(flatItems: FlatItem[]): Item {
  const root: Item = { text: 'root', children: [] }
  const stack: Item[] = []

  const firstChildOfRoot = {
    text: flatItems[0].text,
    children: [],
  }
  root.children.push(firstChildOfRoot)
  stack.push(root)
  stack.push(firstChildOfRoot)

  for (let i = 1; i < flatItems.length; i++) {
    const flatItem = flatItems[i]
    const depthDiff = flatItem.depth - (stack.length - 1)
    if (depthDiff <= 0) {
      removeFromEnd(stack, -depthDiff + 1)
    }
    const stackTop = stack[stack.length - 1]
    const newEl = {
      text: flatItem.text,
      children: [],
    }
    stackTop.children.push(newEl)
    stack.push(newEl)
  }

  return root
}

function removeFromEnd<T>(array: T[], count: number) {
  array.splice(array.length - count, count)
}

The idea is to maintain the stack of recently pushed elements. When going one level deeper, we add to the stack. When going n levels, we pop n items out of the stack firstly, before adding a child to the current top of the stack. There's also some “plus/minus one” correction here because your depths are starting at 1 instead of 0. Also, there's an additional wrapper element called root, which you do not have to print later, but serves as the umbrella element holding together all your depth: 1 elements (basically, a fictional depth: 0 which you assume exists, it's the root of the forest).

I've implemented the above on StackBlitz in pure TypeScript (just the algorithm, no Angular), and added a dummy "rendering" methods which prints a string.

const flatItems: FlatItem[] = [
  { text: 'A', depth: 1 },
  { text: 'B', depth: 1 },
  { text: 'C', depth: 2 },
  { text: 'D', depth: 2 },
  { text: 'E', depth: 3 },
  { text: 'F', depth: 2 },
  { text: 'G', depth: 2 },
  { text: 'H', depth: 3 },
  { text: 'I', depth: 4 },
  { text: 'J', depth: 4 },
  { text: 'K', depth: 4 },
  { text: 'L', depth: 2 },
  { text: 'M', depth: 1 },
  { text: 'N', depth: 2 },
  { text: 'O', depth: 3 },
  { text: 'P', depth: 1 },
  { text: 'Q', depth: 2 },
  { text: 'R', depth: 3 },
  { text: 'S', depth: 4 },
  { text: 'T', depth: 4 },
  { text: 'U', depth: 4 },
  { text: 'V', depth: 3 },
  { text: 'W', depth: 3 },
]

-A
-B
---C
---D
-----E
---F
---G
-----H
-------I
-------J
-------K
---L
-M
---N
-----O
-P
---Q
-----R
-------S
-------T
-------U
-----V
-----W

Now to create DOM elements from these, you'll need to create a recursive component which calls itself on and on, unless there are no children in the element (which is how you break out of the recursion).

Note that your example markup is incorrect. You cannot nest ol directly into ol. You need to have a li element, with its own text, and then add the next ol inside. See Proper way to make HTML nested list? for more details on how to correctly mark up nested lists.

This is untested, but it should go along the following lines. The component's selector is tree-view, but you can obviously change this to whatever you need. The component class implements the interface of Item from the code above.

<li>{{ text }}</li>
<ol *ngIf="children.length > 0">
  <tree-view
    *ngFor="let child of children"
    [children]="child.children"
    [text]="child.text"
  ></tree-view>
</ol>

You'll also need additional *ngIf to switch between printing ol or ul depending on the type which I've left out, but this is a trivial task.

Lazar Ljubenović
  • 18,976
  • 10
  • 56
  • 91
  • Thanks so much, It worked. Can you remove the `root` element coz it add addition element to the dom – stack s Apr 29 '19 at 10:59
  • Add an `isRoot` input to the recursive component and make its default value `true`. Then, when you use the recursive component _inside_ the recursive component itself, pass in `false`. Then use `*ngIf` to control rendering the wrapper. – Lazar Ljubenović Apr 29 '19 at 11:02
  • Can you check this, https://stackblitz.com/edit/angular-list-component?file=src%2Fapp%2Fapp.component.ts – stack s Apr 29 '19 at 11:05
  • @stacks It seems that the result is what you needed. What's the issue? – Lazar Ljubenović Apr 29 '19 at 12:05
  • It as an issue, if it is `unordered-list-item` it still shows as `orderered-list-item` – stack s Apr 29 '19 at 12:09
  • That depends on how you structure your data. You declare the item type repeatedly instead of list type once, which doesn't make much sense it will just be repeated over and over for every item. Still, you can simply do a condition for "children[0].type" when creating the list. – Lazar Ljubenović Apr 29 '19 at 18:02
-2

Try This:

let orderedData = data.filter((oData) => {
    return (oData.depth == 2)
});

let unorderedData = data.filter((unoData) => {
    return (oData.depth == 1)
});

<ul>
    <li *ngFor="let item of unorderedData ">{{item.text}}</li>
    <ol>
        <li *ngFor="let item of orderedData">{{item.text}}</li>
    </ol>
</ul>
Dhaval
  • 868
  • 12
  • 22