取消订阅服务内部的 Observables?

Unsubscribing from Observables inside services?

在查看代码示例时,我经常看到服务内部的可观察对象未取消订阅的情况。

这是一个例子:

export class AuthGuard implements CanActivate {

    private isLoggedIn: boolean;
    private isLoggedIn$: Observable<boolean>;


    constructor(private authService: AuthService, private router: Router) {
        this.isLoggedIn$ = this.authService.isLoggedIn();

        this.isLoggedIn$.subscribe(res => {
            if (res) {
                this.isLoggedIn = true;
            } 
            else {
                this.isLoggedIn = false;
            }
        });
    }

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        if (this.isLoggedIn) {
            return true;
        }
        else {
            this.router.navigate(['login']);
            return false;
        }     
    }
} 

在这种情况下,您是否有理由不取消订阅 observable this.isLoggedIn$?或者上面的例子只是糟糕的编码导致内存泄漏?

对于核心服务来说,取消订阅并不是真正必要的,因为服务与应用程序一样存在,而且服务是单例的。问题是你必须确保你总是将它作为单例使用(为了更好的解决方案,请阅读下文)

对于组件服务,取消订阅应该放在服务的 ngOnDestroy 中,因为服务实际上可以实现这个 NgOnDestroy 钩子。更好的方法是使用 takeUntil(this.destroy) 管道并在销毁时发出它。

更好的方法是在模板中使用 async 管道,永远不要直接订阅这些内容。

另一方面,在你的守卫中,你可以使用 take(1) 管道,这将接收第一个发射并立即取消订阅,而无需你这样做,这会将你的示例更改为此。可以看到,代码里面没有subscribe:

export class AuthGuard implements CanActivate {

  constructor(private authService: AuthService, private router: Router) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    return this.authService.isLoggedIn().pipe(
      take(1),
      tap((loggedIn) => {
        if (!loggedIn) {
          this.router.navigate(['login'])
        }
      })
    );    
  }
}

最重要的是,尝试使用模板内部异步和 rxjs 管道函数让您的应用程序保持 Observable 流,而不是订阅和存储子结果。要使 isLoggedIn() 在订阅时发出最后一个结果,您可以使用 shareReplay() 管道或使其成为 BehaviourSubject 开始。

When looking at code examples, I often see the case where observables inside services are not unsubscribed from.

您正在查看一个错误。

Is there a reason why you would not unsubscribe from the observable this.isLoggedIn$ in this case?

如果你想泄漏内存。

Or is the above example just bad coding leading to memory leaks?

是的,这是内存泄漏。 this引用的对象销毁后,订阅函数会继续执行。如果这个对象永远不会被销毁,它可能永远不会发生,但它是一个代码示例,它会在临时创建对象的单元测试中失败。

@Injectable() 标记的对象通常表现得像单例或具有弱引用。它可能会工作一段时间,但是一旦你在临时情况下使用它,它就会泄漏内存。