Angular递归Tree组件如何实现nodeSelected事件仅触发一次?
Hey there! Let's break down why you're seeing multiple event triggers and fix it step by step.
The Root Cause
Your current setup creates a chain of unnecessary event forwarding:
- When you click node C, its
treeNodeSelectedmethod runs, logs "node" and emits the event. - The parent Tree component (for node B) has
(nodeSelected)="treeNodeSelected($event)", so it calls its owntreeNodeSelected—logging again and emitting the event. - This repeats for the grandparent Tree component (node A), leading to 3 "node" logs before the event finally reaches your root
actual-component.
Simple Fix: Forward Child Events Directly (No Extra Logging)
Instead of passing child component events through your treeNodeSelected method (which adds duplicate logs), forward them directly to the parent using nodeSelected.emit($event) in the template. Here's how to update your code:
1. Update the Tree Component's HTML
Remove the call to treeNodeSelected for child tree components, and emit the event directly:
<ul> <li *ngFor="let item of node"> <div> <span (click)="treeNodeSelected(item)">{{item.name}}</span> </div> <!-- Forward child nodeSelected events directly without extra processing --> <tree *ngIf="item.child.length > 0" [node]="item.child" (nodeSelected)="nodeSelected.emit($event)"> </tree> </li> </ul>
2. Clean Up the Tree Component's TS
Your treeNodeSelected method should only handle clicks on the current component's nodes. You can also remove the true from EventEmitter (it enables async mode, which isn't needed here):
@Input("node") node; @Output() nodeSelected = new EventEmitter<any>(); treeNodeSelected(node) { console.log("node", node); this.nodeSelected.emit(node); }
How This Works
- When you click a child node (like C), its
treeNodeSelectedruns once (logging "node" and emitting the event). - Parent components (B and A) simply forward the event up the chain without running their own
treeNodeSelected—so no duplicate logs. - Finally, your
actual-componentreceives the event exactly once, logging "End Component".
Alternative: Use ViewChildren for Programmatic Forwarding
If you prefer to handle event forwarding in the component class instead of the template, you can use ViewChildren to subscribe to child component events:
Tree Component TS:
import { Component, Input, Output, EventEmitter, ViewChildren, QueryList, AfterViewInit } from '@angular/core'; import { TreeComponent } from './tree.component'; // Import your Tree component @Component({ selector: 'tree', templateUrl: './tree.component.html' }) export class TreeComponent implements AfterViewInit { @Input("node") node; @Output() nodeSelected = new EventEmitter<any>(); @ViewChildren(TreeComponent) childTrees: QueryList<TreeComponent>; ngAfterViewInit() { // Subscribe to each child's nodeSelected event and forward it this.childTrees.forEach(childTree => { childTree.nodeSelected.subscribe(node => { this.nodeSelected.emit(node); }); }); } treeNodeSelected(node) { console.log("node", node); this.nodeSelected.emit(node); } }
Tree Component HTML (remove the (nodeSelected) binding):
<ul> <li *ngFor="let item of node"> <div> <span (click)="treeNodeSelected(item)">{{item.name}}</span> </div> <tree *ngIf="item.child.length > 0" [node]="item.child"></tree> </li> </ul>
This achieves the same result but keeps your template cleaner if you have more complex logic to handle later.
Either approach will stop the duplicate event triggers and logs while ensuring the event reaches your root component correctly.
内容的提问来源于stack exchange,提问作者Code Guru

