Search code examples
angulartypescripttreeparent-childngonchanges

ngOnChanges only hitting root of tree, not all offsprings


My parent component has a non binary tree node(the root of the tree) as child. Every node can have 0 or multiple children of type node, and the children can be collapsed or expanded. Additionally, in my parent component, there is button that allows you to Collapse All or Expand All children of all nodes in the tree.

I have an enum that marks whether the entire tree is collapsed(via button), the entire tree is expanded(via button), or if the tree status is controlled manually(which is not relevant for this issue).

The enum looks like this:

export enum TreeStatus{
  Collapsed,
  Expanded,
  Manual
}

parent.component.ts:

export class ParentComponent implements OnInit {
  root: Node;
  public treeStatus= TreeStatus.Collapsed;   //in the beginning, every node in the tree has children collapsed
  public isAnyNodeChildrenCollapsed = true;  //checks if any node has children collapsed(mainly used for manual toggle)

  onExpandAll() {
    this.treeStatus= TreeStatus.Expanded;
    this.isAnyNodeChildrenCollapsed = false;
  }

  onCollapseAll(){
    this.treeStatus= TreeStatus.Collapsed;
    this.isAnyNodeChildrenCollapsed = true;
  }
  
  ...
}

parent.component.html:

<div>
  <button *ngIf="!isAnyNodeChildrenCollapsed " (click)="onCollapseAll()">  //if all nodes have children expanded, we will see the button that allows us to collapse all
    Collapse All
  </button>
  <button *ngIf="isAnyNodeChildrenCollapsed " (click)="onExpandAll()">     //if at least one node has children collapsed, we will see the button that allows us to expand all
    Expand All
  </button>
</div>
<div>
  <tree-item [node]="root" [treeStatus]="treeStatus"></tree-item>            //the root of my tree
</div>

tree-item.component.ts:

export class TreeItemComponent implements OnInit, OnChanges{
   @Input() node: Node;
   @Input() treeStatus: TreeStatus;
   public isChildrenCollapsed = true;  //initially, all nodes have children collapsed

   ngOnChanges(changes: SimpleChanges) {
    console.log(this.node.id + ": " + changes.treeStatus.previousValue + " => " + changes.TreeStatus.currentValue);   
    //this log above best displays the issue. When I click on Expand All, all nodes in my tree appear in the logs, but when I click Collapse All, even though this is the same callback method, only the root appears to detect the change.(even so, the toggle somehow happens regardless)

    if (changes.treeStatus && changes.treeStatus.currentValue!=NavigatorTreeStatus.Manual) {
      this.isChildrenCollapsed = !(changes.treeStatus.currentValue == NavigatorTreeStatus.Expanded);
      }
    }
  }

}

tree-item.component.html(basic DFS tree parsing):

<div>
//display node here
</div>
<div *ngIf="node.children.length > 0 && !isChildrenCollapsed">
  <tree-item *ngFor="let child of node.children"
             [node]="child" 
             [treeStatus]="treeStatus" 
  >
</div>

I simply cannot understand why, when I hit Expand All, thus changing treeStatus to Expanded, the ngOnChanged works properly and as expected, propagating to every node in the three, but when hitting Collapse All now, which triggers the same ngOnChanges callback method, the only node that detects the change seems to be the root.

Here is how the logs appear: Initially, when opening component, root id gets status 0(Collapsed), instead of undefined:

2013777645: undefined => 0

After you hit Expand All, root Id changes value from 0(Collapsed), to 1(Expanded), and the rest of the nodes change from undefined to 1(Expanded)

2013777645: 0 => 1

2118369458: undefined => 1

368241412: undefined => 1

1860836078: undefined => 1

1979885541: undefined => 1

1316570423: undefined => 1

360484732: undefined => 1

506271756: undefined => 1

Now, after hitting Collapse All, only the root appears in the log, changing values from 1(Expanded) to 0(Collapsed).

2013777645: 1 => 0

Finally, if we hit again Expand All now, the exact same logs appear as before, even if the other nodes should not have an undefined value for treeStatus, as it has been assigned a value before.

2013777645: 0 => 1

2118369458: undefined => 1

368241412: undefined => 1

1860836078: undefined => 1

1979885541: undefined => 1

1316570423: undefined => 1

360484732: undefined => 1

506271756: undefined => 1

Can you please help me understand what I am missing? I really hope you can understand the issue from this explanation. Thank you in advance!


Solution

  • Looks like you hide child nodes with ngIf. When ngIf is false you're component (or template) not just hidden, its destroyed. You can find more details here.