LukyFoggy
LukyFoggy

Reputation: 679

Check recursively if any children are active

I've a recursive list of items in Angular/TypeScript and I'm trying to only display items when they are active=true; themselves or if any children or children´s children are active=true;

data.json

[
    {
        "active": true,     // active and some children active => show   
        "items": [
            {
                "active": false,     // not active and no children active => don´t show
                "items": [
                    {
                        "active": false,
                        "items": []
                    },
                    {
                        "active": false,
                        "items": []
                    }
                ]
            },
            {
                "active": false,     // not active but some children active => show   
                "items": [
                    {
                        "active": true,
                        "items": []
                    }
                ]
            },
            {
                "active": true,     // active and some children active => show   
                "items": [
                    {
                        "active": true,
                        "items": []
                    }
                ]
            }
        ]
    }
]

This is my current recursive method, however, it still doesn´t work for nested items and returns false for all parents when I set the deepest item to active=false;

This was because when an item had children, it would just continue the recursion (return this.hasActiveChildren(i);) without taking the current item.active into account.

method.ts

  public hasActiveChildren(item: Item): boolean {
    if (item.items === null || item.items.length <= 0) {
      return false;
    }

    return item.items.some(i => {
      if (i.items === null || i.items.length <= 0) {
        return i.active;
      } else {
        return this.hasActiveChildren(i);
      }
    });
  }

The second method works better and returns false for a parent if all immediate children are active=false;. However, it still doesn´t consider children´s children.

updatedMethod.ts

  public hasActiveChildren(item: Item): boolean {
    for (const i of item.items) {
      if (i.active === true) {
        return true;
      } else if(i.items=== null || i.items.length <= 0) {
        return this.hasActiveChildren(i);
      }
    }

    return false;
  }

Maybe I need to specify:

Upvotes: 1

Views: 1126

Answers (2)

Joshybull
Joshybull

Reputation: 171

The issue with your current approach is that you're only checking if the children are active when the parent is not. You want to check the children recursively every time or before checking if the parent is active. I used the following interface:

export interface Item {
    active: boolean,
    items: Item[]
}

Here's an implementation using filter which will recursively call the function for all children before and return an array of all active items in item.items. The use of || will show this item if any of the children are active OR the current item is active. The key here is that item.active is checked AFTER recursively checking the children.

function shouldShowItem(item: Item): boolean {
    const result: boolean = item.items.filter(i => shouldShowItem(i)).length > 0 || item.active;
    // Logic to display the item here based on result
    return result;
}

Here's another option that might be a bit clearer. The result is initialized to the value of active and then all children are recursively checked. The value is overwritten with true if any of the children are active.

function shouldShowItem(item: Item): boolean {
    let result: boolean = item.active;
    for (let i of item.items) {
        if (shouldShowItem(i)) {
            result = true;
        }
    }
    // Logic to display the item here based on result
    return result;
}

Upvotes: 2

LukyFoggy
LukyFoggy

Reputation: 679

After spending some time I came up with the following recursive method. It seems to work and looks ok in terms of performance.

method.ts

  public hasActiveChildren(item: Item): boolean {
    // if the item has no sub items, always return false
    if (item.items == null || item.items.length < 0) {
      return false;
    }

    for (const i of item.items) {
      // if any sub item is active, always return true
      if (i.active === true) {
        return true;
      } else {
        // else, repeat the process
        return this.hasActiveChildren(i);
      }
    }
    
    // default return value due to compiler errors
    return false;
  }

Edit: It works fine when an item only has maximum one sub item, however, after more in-depth testing, I found out that the method and its return value fail when applied to larger nested structures. So there is still room for improvement.

Upvotes: 1

Related Questions