小心 Angular 中的单例 Service

2

你可能知道,当我们通过@NgModule()装饰器来声明一个service时,它将符合单例模式,同时还意味着它与整个应用的生命周期保持一致。比如:

export class AdminService {
  data = Array(10000).fill(dummy);
}
@NgModule({
  providers: [AdminService, AdminDataService]
})

我们在刚开始接触Angular的时候,总是不计后果的将所有service都使用@NgModule()来声明,这将会造成一个不易发现的问题:

You are not releasing memory.

在上面的例子中,尽管你不再需要这些内存中储存的数据,但是让我们停下来仔细想一想,我们真的需要将一个service声明为单例的吗?

比如,在我们整个应用中,我们会有一个管理区域需要呈现大量的表格数据(同时这些数据只在这个管理区域展现),这些数据会储存在内存中。在这种情况下,我们没有必要将这个service声明为单例的,因为我们不需要缓冲层来缓存这些数据以供应用中的其他模块使用。

进一步讲,当前我们仅仅是想使这些表格数据在多个component之间共享,同时将数据与service中的多个helper方法耦合起来。所以我们完全可以直接使用@Component()装饰器来声明service,这样它就会成为一个非单例service,如下:

@Component({
  selector: 'admin-tab',
  providers: [AdminService, AdminDataService]
})

这样做的好处是,当Angular注销组件实例时,Angular将同时注销与之绑定的service实例,y也会释放那些用来储存数据的内存。

OnDestroy 钩子函数

许多开发者也许不知道非单例servicengOnDestroy()生命周期,所以你也可以在这个生命周期中进行一些销毁逻辑代码的编写,比如:

export class AdminService implements OnDestroy {
  ngOnDestroy() {
    // Clean subscriptions, intervals, etc
  }  
}

另外,如果我们调用NgModuleRef.destroy()或者PlatformRef.destroy(),单例servicengOnDestroy钩子函数也会被[执行]。(https://github.com/angular/an...

译者注

之所以翻译了这篇文章,是因为今天在整理项目代码的时候,偶然发现了这个问题,虽然我使用Angular也有一段时间了,但是依然将很多没有必要声明在NgModule中的服务以单例模式的方式声明了。文章中指出的问题确实是一个重要但又难以发现的问题。

大体总结一下Angular中声明service的不同方式和应用场景。

使用@Component

这时service与组件本身生命周期保持一致,非单例,适合声明一些需要暂存数据的工具类或者仅在某个或某几个组件中需要缓存数据的状态管理类service

使用@NgModuleproviders

这时service与应用本身生命周期保持一致(非懒加载),单例,适合声明一些需要在全局缓存数据的状态管理类service

但是有一个特例,懒加载模块中的service是会在模块加载时重新创建一个实例的,懒加载模块中均会注入后创建的service实例,因此懒加载模块与非懒加载模块间的service非单例。

使用forRoot

使用forRoot可以保证当前模块即使是懒加载模块,在加载时也不会重新创建一个新的service实例,因为懒加载模块在加载时,会临时创建一个从属于根injector的子injector,根据Angular中的依赖注入流程,当尝试通过一个子injector中注入不存在的实例对象时,会尝试向父级injector获取,因此最终可保证该service在应用任何地方被注入均是单例。

关于官方文档的介绍,可以参考ProvidersSingleton Services


如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

载入中...