2
votes

I need to get the path from given node to root in a Angular nested Mat-Tree.

Preferably starting from root until I get to my chosen node. Note that the chosen node can be repeated in the multiple leaves of the tree (with the same node name).

The answer might look like a 2D array in which the first dimension is an array of paths and seconds dimension is the array containing the nodes in the path.

I 'm not really comfortable with recursive functions can someone please help me figure this one out?

An example of the tree structure from Angular.io can be found on stackblitz. Just imagine that the fruit or vegetables in this example could be repeated in multiple categories.

EDIT1: Clarification, more context, added example link

2
An example would be great - Kenny
@Kenny done. please take another look - Sev
Did you ever figure it out? I had written some custom code to do it, but could not figure it out with the given API - ahong
Sorry for the late reply. Yeah I figured out a few ways to do it by writing some helper functions. I've attached those as the answer to this post. Hope it's still useful to you :) - Sev

2 Answers

1
votes

If you are using nested tree, you might want to have a look at an example given in the mat-tree section in the docs. Which is: Tree with checkboxes from the examples

Now moving to your query, You can use angular map structure as used in above link. Flatmap or NestedMap which can help you to get parent node from child or vice versa. The code given is rather complex but worth to experiment with.

Example:

// This gets the level of node.
_getLevel = (node: ItemFlatNode) => node.level;

If you want to get the parent node:

  /**
   * Gets Parent node in form of ItemFlatNode
   */
  getParentNode(node: ItemFlatNode): ItemFlatNode | null {
    const currentLevel = this.
    _getLevel (node);

    if (currentLevel < 1) {
      return null;
    }

    const startIndex = this.treeControl.dataNodes.indexOf(node) - 1;

    for (let i = startIndex; i >= 0; i--) {
      const currentNode = this.treeControl.dataNodes[i];

      if (this._getLevel (currentNode) < currentLevel) {
        return currentNode;
      }
    }
    return null;
  }

There are some built-in functions already given. But you might have to modify them as per your requirements. Hope this helps.

0
votes

I figured out the answer myself after some thought. Hope it will be useful to someone else too.

/**
 * IMPORTANT!
 * The recursive functions below are too heavy to be calculated on the front-end.
 * Ideally they need to become APIs in the back-end
 */
 class ProductTreeModel {
    id: string;
    name: string;
    parentId: string;
    parentName: string;
    isProduct: boolean;
    children: ProductTreeModel[];
    treeId?: string;
    productId?: string;
    expandable?: boolean;
    level?: number;
    data?: Object;
}

/**
 * function traverses top-down through a nested mat-tree with a single root
 * and returns the path from root to a product based on nodeId
 * The path is an array of nodes starting from root and ending in product
 * @param rootNode
 * @param nodeId
 * @param pathFromRoot
 */
const getProductPathFromSingleRootNested = (
    rootNode: ProductTreeModel,
    nodeId: ProductTreeModel['id'],
    pathFromRoot: ProductTreeModel[]
): ProductTreeModel[] => {
    if (rootNode === null || (!rootNode.children && rootNode.id !== nodeId))
        return null;

    if (rootNode.id === nodeId) {
        pathFromRoot.push(rootNode);
        return pathFromRoot;
    }

    let isChildPartOfPath = false;
    for (const child of rootNode.children) {
        isChildPartOfPath = getProductPathFromSingleRootNested(
            child,
            nodeId,
            pathFromRoot
        )
            ? true
            : false;
    }
    if (isChildPartOfPath) {
        pathFromRoot.push(rootNode);
        return pathFromRoot;
    }

    return null;
};

/**
 * function traverses top-down through a nested mat-tree with multiple roots
 * and returns the path from root to a product based on productId
 * The path is an array of nodes starting from root and ending in product
 * Note: This function returns multiple paths for the same product that is listed under multiple roots
 * @param rootNodes
 * @param productId
 * @param pathFromRoot
 */

export const getProductPathFromMultipleRootsNested = (
    rootNodes: ProductTreeModel[],
    nodeId: ProductTreeModel['id'],
    pathFromRoot: ProductTreeModel[] = []
): ProductTreeModel[] => {
    let isPathFound = false;
    let fistPathFromRoot = null;

    // let counter = 0;
    for (let i = 0; i < rootNodes.length && !isPathFound; i++) {
        fistPathFromRoot = getProductPathFromSingleRootNested(
            rootNodes[i],
            nodeId,
            pathFromRoot
        );
        // counter++;
        if (fistPathFromRoot) isPathFound = true;
    }

    return fistPathFromRoot;
};