展开 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