服务是一个很好的解决方案,但您想了解原因:
TL/DR 解释:
在调用 ngOnDestroy() 之前,该订阅已为您取消订阅。之所以会发生这种自动化,是因为您订阅了一个模板,对模板进行了角度解析,并且足够了解这样做。
更详细的解释:
Angular为每个组件生成一个工厂,这个过程称为代码生成,Angular创建的工厂函数返回一个根据你的组件元数据创建的唯一View类型的实例,继承AppView 类。
组件实例的整个生命周期由视图管理。例如,变更检测,当然还有生命周期挂钩。
在调用 ngOnDestroy 生命周期钩子之前(通过组件的内部视图)调用内部销毁函数,它会为您做一些清理工作。
清理操作之一是取消订阅所有事件,例如单击、鼠标移动和 自定义事件,这些事件是在子组件中定义的 @Output 发射器。
这是静态完成的,这意味着代码生成器知道事件订阅,因此它添加了硬编码逻辑来删除它。
重要的是,这可以节省大量样板代码并防止内存泄漏 - 框架会为您清理。
同样重要的是要注意 Angular 可以做到这一点,因为它有很多关于正在发生的事情的元数据。
它知道它是一个订阅,因为它解析 Template 并找到一个事件表达式(例如:(click)="something()"),还因为 MyComp 中的 @Output 装饰器
在 AppView 类中可以很清楚地看到逻辑 - LINK
每个视图都继承自 AppView 并实现(除其他外)一个名为 destroyInternal 的函数。
顺序是在销毁时调用 destroyLocal()(定义在 AppView 上),destroyLocal 将清理所有订阅和一次性项目,然后它会调用 destroyInternal、然后 destroyInternal 将在您的组件实例上调用 ngOnDestroy()。
解决方案:
现在,服务可能是一个很好的解决方案,但如果您了解发生了什么,您可以在没有外部服务帮助的情况下解决这个问题,这非常简单。
正如我们现在所知,Angular 将取消订阅模板中注册的订阅,如果我们手动注册(即:使用代码),我们将能够触发事件。
虽然简单,但它有一些样板,因为我们需要:
- 我们需要获取对子组件的引用,我们将使用 ViewChild
- 我们需要订阅子组件实例上的事件,但我们只有可以在
ngAfterViewInit 生命周期钩子被触发后完成。
- 我们需要在完成后取消订阅,这可以通过多种方式完成,最好的方法是完成 EventEmitter - 我们将在定义 EventEmitter 的组件中简单地通过致电
myEvent.complete()。它会为我们处置一切。
这里是销毁函数:
ngOnDestroy() {
this.myEvent.emit('ngOnDestroy hook');
this.myEvent.complete();
}
这里是 MyApp 组件:
export class MyApp {
displayComp = true
@ViewChild('mycomp', {read: MyComp}) myComp: MyComp;
constructor() {}
ngAfterViewInit() {
this.myComp.myEvent.subscribe( () => alert('myApp > receive event via MANUAL Subscription'));
}
test(e) {
alert('myApp > receive event');
}
}
这是一个工作的 plunker - LINK
更新:我刚刚在 Angular 存储库中注意到此问题的 pending PR,现在 12 天仍未合并,合并社区贡献需要时间,所以希望它会在下一个 RC 中落地。