Angular父组件按钮click事件第二次点击未触发问题求解
问题根因分析
- 核心是事件触发顺序和Angular变更检测导致的DOM重建冲突,具体流程如下:
- 当child-container、grandson组件渲染时,textarea处于聚焦状态,此时你第二次点击父组件的按钮,事件执行顺序为:
mousedown(按钮上) →textarea.focusout→mouseup→click(按钮上) focusout事件同步触发后,会执行绑定的回调把object.showChildContiner设为false,触发Angular变更检测- 你的
*ngFor没有配置trackBy函数,当objects$通过ngrxPushemit新数组时,Angular会认为所有列表项都是新的,直接销毁原有列表项的全部DOM元素(包括你点击的那个按钮) - 后续要触发的
click事件对应的原按钮DOM已经被销毁,自然不会触发绑定的onAddBtnClick回调
- 当child-container、grandson组件渲染时,textarea处于聚焦状态,此时你第二次点击父组件的按钮,事件执行顺序为:
- 额外的代码问题:当前组件Output绑定不匹配,
grandson组件的Output名为onGrandSonEmit,但child-container模板里绑定的是(containerClickOutside),且child-container本身也没有声明containerClickOutside这个Output,事件传递链路本身存在bug。
修复方案
1. 给*ngFor添加trackBy函数(必改,解决DOM意外销毁问题)
在父组件TS中添加trackBy方法:
trackByObjectId(index: number, obj: any): number { return obj.id; // 替换为你的object的唯一标识字段 }
修改父组件模板的*ngFor声明:
<div *ngFor="let object of objects$ | ngrxPush; trackBy: trackByObjectId">
该改动可以保证修改object的属性时,不会销毁重建列表项的DOM,按钮元素会一直存在,click事件可以正常触发。
2. 调整focusout的执行时机(解决事件顺序冲突)
把grandson组件的focusout emit逻辑改成异步执行,让click事件先执行完成后再修改状态:
onFocusOut(): void { setTimeout(() => { this.onGrandSonEmit.emit(this.textarea.nativeElement.value); }, 0); }
也可以改用cdkClickOutside指令来检测点击容器外的操作,替代focusout的实现,更符合点击外部关闭的交互逻辑。
3. 修复事件传递链路的绑定错误
首先在child-container组件中声明Output:
@Output() containerClickOutside = new EventEmitter<string>();
然后修改child-container模板的事件绑定,匹配grandson的Output名称:
<app-grandson-component *ngIf="viewChildContainer" (onGrandSonEmit)="onGrandSonEmit($event)" > </app-grandson-component>
内容的提问来源于stack exchange,提问作者DaVinci005
相关产品推荐
相关产品推荐

