展开 dynamically-loaded 嵌套树节点时如何等待 children 加载完成
how to wait for children load to complete when expanding a dynamically-loaded nested tree node
我是 angular 的新手,所以我确定我只是在这里遗漏了一些相当简单的东西,但我还没有在任何地方找到我正在做的事情来回答我的问题。
我使用服务成功地在 nested angular material 树中动态加载分支。我通过定义一个 'loadChildren' 函数来实现这一点,该函数调用服务来填充 children 数组,在实例化时绑定到我的 NestedTreeControl object,基本上:
public nestedTreeControl: NestedTreeControl<Treenode>;
. . .
constructor() {
this.nestedTreeControl = new NestedTreeControl<Treenode>(this.loadChildren);
并用 ngIf 测试包装我的 ng-container,以便嵌套组件在完全加载之前不会实例化:
<ul *ngIf="nestedTreeControl.isExpanded(tnode) && tnode.childrenLoaded">
<ng-container matTreeNodeOutlet></ng-container>
</ul>
这在我的 UI 中完美运行,可以动态扩展和加载 children。但是当我有一个已知 'nodeId' 值的有序数组(一个参数是 Treenode [=49= 的一部分时,我试图添加一个实用函数来递归扩展节点] 上面)但是如果路径中的所有节点都没有在我的树中展开,列表中的 nodeIds 可能不会全部加载。
所以我有一个递归函数,类似于此处 exp() 方法中显示的内容:https://stackblitz.com/edit/angular-icfxva?file=src%2Fapp%2Ftree-nested-overview-example.ts, which I found from a comment on this post: (How to expand material nested tree to specific node?).
如果我之前在我的 UI 中扩展了我的树以包含有序搜索数组中的所有 nodeId,我的函数运行良好。但是由于我的 children 在扩展节点时动态加载,所以当我扩展 non-expanded 节点时(即 nestedTreeControl.expand(tnode)
(或在 stackblitz 示例中,treeControl.expand(node)
) ,在进行递归调用并传入 child 节点列表之前,我需要等待 children 加载。
我不确定如何 'waiting' 让 children 在进行下一次调用之前加载到我的递归函数中。 What/how 我可以绑定到 NestedTreeControl 以确定 children 何时完成了 newly-expanded 节点的加载(即,当我的节点的 'childrenLoaded' 标志最终被设置时到 'true')?
经过一段时间的研究,我找到了答案。我最后添加了一个 EventEmitter 实例,一旦子项完全加载,该实例就会在 loadChildren 方法中发出。
在我的递归函数中,我正在订阅新事件并等待递归调用自身直到接收到事件,但前提是 'childrenLoaded' 标志尚未在我的节点对象上设置,如果节点未展开,我会在展开节点之前立即执行 check/subscription。对于所有其他情况(先前加载的子项,无论是否扩展),我不订阅但立即进行递归调用。
此外,我没有在我的递归函数中传回最终找到的节点,而是发出了第二个 EventEmitter,递归函数的原始第一个调用者正在订阅它。
这对于深度嵌套、动态加载的树似乎非常有效。
原则上你可以这样做:
async function expandToNode(node: TreeNode, p: (n: TreeNode) => boolean): Promise<void> {
if (p(node)) {
return;
}
await expandOneNode(node); // this will populate the `children` field asynchronously
const promises = node.children!.map(r => expandToNode(r, p));
await Promise.all(promises);
}
一些用于演示目的的虚拟结构:
// This is just a dummy interface for demo purposes, yours is different.
interface TreeNode {
id: string;
children?: TreeNode[];
data?: any;
}
// Simulate async expansion of node that takes 1 second to complete
async function expandOneNode(node: TreeNode): Promise<void> {
return new Promise(resolve => {
setTimeout(() => {
node.children = []; // just an example
resolve();
}, 1000);
});
}
用法:
await expandToNode(root, n => n.data?.foundGold); // dummy criterion for demo purposes
我是 angular 的新手,所以我确定我只是在这里遗漏了一些相当简单的东西,但我还没有在任何地方找到我正在做的事情来回答我的问题。
我使用服务成功地在 nested angular material 树中动态加载分支。我通过定义一个 'loadChildren' 函数来实现这一点,该函数调用服务来填充 children 数组,在实例化时绑定到我的 NestedTreeControl object,基本上:
public nestedTreeControl: NestedTreeControl<Treenode>;
. . .
constructor() {
this.nestedTreeControl = new NestedTreeControl<Treenode>(this.loadChildren);
并用 ngIf 测试包装我的 ng-container,以便嵌套组件在完全加载之前不会实例化:
<ul *ngIf="nestedTreeControl.isExpanded(tnode) && tnode.childrenLoaded">
<ng-container matTreeNodeOutlet></ng-container>
</ul>
这在我的 UI 中完美运行,可以动态扩展和加载 children。但是当我有一个已知 'nodeId' 值的有序数组(一个参数是 Treenode [=49= 的一部分时,我试图添加一个实用函数来递归扩展节点] 上面)但是如果路径中的所有节点都没有在我的树中展开,列表中的 nodeIds 可能不会全部加载。
所以我有一个递归函数,类似于此处 exp() 方法中显示的内容:https://stackblitz.com/edit/angular-icfxva?file=src%2Fapp%2Ftree-nested-overview-example.ts, which I found from a comment on this post: (How to expand material nested tree to specific node?).
如果我之前在我的 UI 中扩展了我的树以包含有序搜索数组中的所有 nodeId,我的函数运行良好。但是由于我的 children 在扩展节点时动态加载,所以当我扩展 non-expanded 节点时(即 nestedTreeControl.expand(tnode)
(或在 stackblitz 示例中,treeControl.expand(node)
) ,在进行递归调用并传入 child 节点列表之前,我需要等待 children 加载。
我不确定如何 'waiting' 让 children 在进行下一次调用之前加载到我的递归函数中。 What/how 我可以绑定到 NestedTreeControl 以确定 children 何时完成了 newly-expanded 节点的加载(即,当我的节点的 'childrenLoaded' 标志最终被设置时到 'true')?
经过一段时间的研究,我找到了答案。我最后添加了一个 EventEmitter 实例,一旦子项完全加载,该实例就会在 loadChildren 方法中发出。
在我的递归函数中,我正在订阅新事件并等待递归调用自身直到接收到事件,但前提是 'childrenLoaded' 标志尚未在我的节点对象上设置,如果节点未展开,我会在展开节点之前立即执行 check/subscription。对于所有其他情况(先前加载的子项,无论是否扩展),我不订阅但立即进行递归调用。
此外,我没有在我的递归函数中传回最终找到的节点,而是发出了第二个 EventEmitter,递归函数的原始第一个调用者正在订阅它。
这对于深度嵌套、动态加载的树似乎非常有效。
原则上你可以这样做:
async function expandToNode(node: TreeNode, p: (n: TreeNode) => boolean): Promise<void> {
if (p(node)) {
return;
}
await expandOneNode(node); // this will populate the `children` field asynchronously
const promises = node.children!.map(r => expandToNode(r, p));
await Promise.all(promises);
}
一些用于演示目的的虚拟结构:
// This is just a dummy interface for demo purposes, yours is different.
interface TreeNode {
id: string;
children?: TreeNode[];
data?: any;
}
// Simulate async expansion of node that takes 1 second to complete
async function expandOneNode(node: TreeNode): Promise<void> {
return new Promise(resolve => {
setTimeout(() => {
node.children = []; // just an example
resolve();
}, 1000);
});
}
用法:
await expandToNode(root, n => n.data?.foundGold); // dummy criterion for demo purposes