首先,您需要将扁平数组转换为嵌套数组。然后你就可以递归循环它来创建你需要的结构。
这是从原始结构构建扁平数组的解决方案。为简单起见,我删除了 type,您还需要在应用程序中维护它,以便您以后可以选择是使用 ol 还是 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)
}
这个想法是维护最近推送的元素的堆栈。当更深一层时,我们添加到堆栈中。当进入n 级别时,我们首先将n 项目从堆栈中弹出,然后将一个子项添加到当前堆栈顶部。这里还有一些“加/减”修正,因为你的深度从1 开始,而不是0。此外,还有一个名为 root 的附加包装元素,您以后不必打印它,但可以用作将所有 depth: 1 元素组合在一起的伞元素(基本上,您假设存在一个虚构的 depth: 0,它是森林的根)。
我已经在纯 TypeScript 中实现了上述 on StackBlitz(只是算法,没有 Angular),并添加了一个打印字符串的虚拟“渲染”方法。
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
现在要从这些元素中创建 DOM 元素,您需要创建一个递归组件,它会不断调用自身,除非元素中没有子元素(这是您摆脱递归的方式)。
请注意,您的示例标记不正确。您不能将ol 直接嵌套到ol 中。你需要有一个li 元素,有它自己的文本,然后添加下一个ol inside。有关如何正确标记嵌套列表的更多详细信息,请参阅 Proper way to make HTML nested list?。
这是未经测试的,但它应该遵循以下几行。组件的选择器是tree-view,但您显然可以将其更改为您需要的任何内容。组件类实现了上面代码中Item的接口。
<li>{{ text }}</li>
<ol *ngIf="children.length > 0">
<tree-view
*ngFor="let child of children"
[children]="child.children"
[text]="child.text"
></tree-view>
</ol>
您还需要额外的*ngIf 来在打印ol 或ul 之间切换,具体取决于我遗漏的类型,但这是一项微不足道的任务。