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.