【问题标题】:Angular Service destroyed, but why is subscription still receiving from observable?Angular Service 被销毁,但为什么订阅仍然从 observable 接收?
【发布时间】:2020-04-05 20:44:24
【问题描述】:

使用 Angular,我有一个 在组件中提供的服务。服务有一个 observable,并且组件订阅了这个 observable。我曾期望我不需要取消订阅订阅/可观察的服务,因为服务应该与组件一起销毁,因此也应与可观察的一起销毁。然而,快速测试表明 observable 是活着的。

在我的视线之外发生了什么? observable 是否在服务之外运行?或者当提供服务的组件被破坏时,服务实际上并没有被破坏?

【问题讨论】:

  • 当组件被销毁时服务不会停止存在。 Useful resource
  • 是的,为什么服务也要销毁?服务默认是单例的,不是每次注入都生成
  • @canbax 在组件级别提供的服务被破坏。这意味着他们可以实现 ngOnDestroy 钩子。但这并不意味着它会自动清除所有订阅,特别是如果我们在内部使用 setInterval 创建 Observable 并且不使用 clearInterval 返回 clear 方法
  • @yurzui 你是对的。默认情况下,它们在根级别提供。我不太了解在组件级别提供它们。
  • @canbax 我在问题In my component 中注意到... providers: ...

标签: angular rxjs


【解决方案1】:

首先要注意的是,Angular Service 是单例的,不会被销毁。

其次,您的 observable 订阅在您的组件中,而不是在您的服务中。 最后回答你的问题,

当组件被销毁时订阅不会被销毁。 您将不得不手动取消订阅。 有几种方法可以做到这一点,最简单的是

- 不要订阅,而是在模板中使用异步管道。

Read this blog post in medium 了解更多关于取消订阅组件所有订阅的更好方法

【讨论】:

  • 如果它是在组件级别提供的,我确实会被销毁。
  • 我不喜欢这条评论Dont subscribe, instead use Async pipe in your template.。很多情况下不能使用异步管道。
  • 是的,在某些情况下我们不能使用异步管道,对于那些订阅,我们必须确保我们在 ngDestroy 上取消订阅。
  • 如果它们在根级别提供,则它们是单例的。
【解决方案2】:

根据 yurzui 的 cmets,我得出以下结论:

尽管服务被销毁(因为它是在被销毁的组件中提供的),但 observable 和订阅继续在服务和组件之外工作。我认为这在某些时候不会被收集,所以我明确地清理它。

我的问题不是如何取消订阅 observable,而是无论如何我认为分享我的实际解决方案来管理我预计会被销毁的订阅会很有用。我在ngOnDestroy 循环中父组件调用的服务上创建了一个destroy() 函数。在这个函数中,我从服务中所有无限的 observables 发出 complete 。 这使我不必重复自己和取消订阅所有子组件。

为我服务:

private subject = new BehaviorSubject<string>(null);

public testObservable(): Observable<string> {
    // ... 
    this.subject.next('result');
    return this.subject.asObservable();
}

destroy() {
    this.subject.complete();
}

在我的组件中:

ngOnInit() {
   this.testService.testObservable().subscribe(data => console.log(data));
}

ngOnDestroy() {
    this.testService.destroy();
}

编辑

我已经包含了一个有效的堆栈闪电战,以防我的解决方案存在一些不确定性:https://stackblitz.com/edit/destroyservice。我喜欢它的是我用 3 行代码取消了 6 个订阅,并且我不需要在任何子组件中包含 ngOnDestroy。

【讨论】:

  • 这不是服务的解决方案,服务是可重用的,这使得它只能使用一次。一旦被销毁,该服务将无法工作下一个注入请求。
  • 这不是真的,正如我的问答中所述,服务是在组件中提供的,下次初始化组件时可以重用。
  • 一个服务应该被设计成可重用的,假设它要在组件中提供是一个糟糕的设计。您也可以只在组件中包含逻辑,因为服务是为共享对象设计的。
  • 为什么在组件中提供服务是一个糟糕的设计?如果这么糟糕,为什么 Angular 被设计为在组件级别提供服务?该服务也是可重用的,它的实例用于父组件及其嵌套组件。
  • 在组件中提供服务很好,就是服务的设计就是我说的不好的设计。
【解决方案3】:

如果您订阅了未完成的可观察对象,则会发生内存泄漏。即使服务是由组件提供的,您在服务中创建了一个 observable 并将其返回给组件。现在 observable 被组件引用了,不再与创建它的服务有任何关系。当组件被销毁时,它将被标记为垃圾回收,但仍然存在于内存中,直到垃圾回收器清理资源。您仍然需要取消订阅未完成的 observables。

有几个选项

  1. 使用异步管道,因为它将为您管理订阅和取消订阅
  2. 保留对订阅的引用并在 ngOnDestroy 上调用 unsubscribe
  3. 对主题使用 take until 并让主题发出 ngOnDestroy。

takeUntil 的好处是您可以使用它来管理多个订阅,而不必单独跟踪每个订阅。

1:异步管道

data$ = this.testService.testObservable();

在视图中

<ng-container *ngIf="data$ | async as data">
  {{ data | json }}
</ng-container>

2:退订

this.subscription = this.testService.testObservable().subscribe(data => console.log(data));

ngOnDestroy() {
  if (this.subscription) {
    this.subscription.unsubscribe();
  }
}

3:采取Until

finalise = new Subject<void>();

this.subscription = this.testService.testObservable().pipe(takeUntil(this.finalise)).subscribe(data => console.log(data));

ngOnDestroy() {
  this.finalise.next();
  this.finalise.complete();
}

【讨论】:

    【解决方案4】:

    将 takeUntil 用于您的场景。您需要创建/初始化一个主题,该主题将在组件销毁时完成。

    private unsubscribe: Subject<void> = new Subject<void>();
    
    
    this.testService.testObservable().pipe(takeUntil(this.unsubscribe))
    .subscribe(data => console.log(data));
    

    完全订阅组件销毁

    使用 subject 和 takeUntil,您可以在 ngOnDestory 一次性取消订阅多个 observable。

    public ngOnDestroy(): void {
        this.unsubscribe.next();
        this.unsubscribe.complete();
      }
    

    【讨论】:

    • 但是我仍然需要取消订阅每个订阅 observable 的组件。对吗?
    • 是的,因为它们是单独的订阅。
    【解决方案5】:

    当我们返回或使用间隔时,observable 将持续接收流,直到我们停止它。为了获得最佳实践,您应该使用 interval observable 和 RxJS 运算符来自动销毁订阅。

    // RxJS v6+
    import { interval, timer } from 'rxjs';
    import { takeUntil } from 'rxjs/operators';
    
    //emit value every 1s
    const source = interval(1000);
    //after 5 seconds, emit value
    const timer$ = timer(5000);
    //when timer emits after 5s, complete source
    const example = source.pipe(takeUntil(timer$));
    //output: 0,1,2,3
    const subscribe = example.subscribe(val => console.log(val));
    

    [注意] 如果您使用 setInterval,则需要使用 ngOnDestroy 显式取消订阅

    【讨论】:

    • 我的问题中的计时器只是为了检查 observable 是否还活着。
    • 那么只有当你的服务是单例的时候才会出现这种情况。你在使用providerIn:root。
    • 你是说我的 observable 没有被破坏,只是因为我实现了 setInterval 作为我的测试用例?或者其他 observables 在销毁服务时也会保持活动状态?
    • 不,我说的是用例。您的服务是在根目录下提供的并且表现得像单例。在这种情况下,您的可观察订阅将持续存在
    猜你喜欢
    • 2018-09-18
    • 1970-01-01
    • 2018-10-17
    • 2014-04-02
    • 1970-01-01
    • 1970-01-01
    • 2022-01-18
    • 2015-06-10
    • 1970-01-01
    相关资源
    最近更新 更多