小心 Angular 中的单例 Service

原文: Angular Services do NOT have to be Singletons

你可能知道,当我们通过@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/angular/blob/674c3def319e2c444823319ae43394d46f3973b7/packages/core/src/view/ng_module.ts#L199-L204)。

译者注

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

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

使用@Component

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

使用@NgModuleproviders

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

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

使用forRoot

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

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏互联网技术杂谈

beanstalkc Tutorial 中文版

英文原版:https://github.com/earl/beanstalkc/blob/wip-doc-rtfd/doc/tutorial.rst

35680
来自专栏mathor

第1章 必须掌握的Linux命令

 命令对象一般是指要处理的文件、目录、用户等资源,而命令参数可以用长格式(完整的选项名称),也可以用短格式(单个字母的缩写),两者分别用--与-作为前缀(示例请...

13510
来自专栏C/C++基础

进程与线程的区别

在开发工作中,尤其是对负载较大的服务端程序的开发,为充分发挥处理器多核性能,提高硬件资源利用率,增加系统吞吐量,少不了并发编程。并发编程一般通过多进程和多线程的...

9730
来自专栏架构说

关于 liunx select/recv函数返回值理解

今天遇到下面问题 如果socket客户端进程挂点 或者正常close 服务端检测 select检测返回的是0 还是-1 还是大于1呀 这个基本问题 竟然我分...

44650
来自专栏java一日一条

servlet/filter/listener/interceptor区别与联系

由于最近两个月工作比较清闲,个人也比较“上进”,利用工作空余时间,也继续学习了一下,某天突然想起struts2和struts1的区别的时 候,发现 为什么st...

6620
来自专栏Linux驱动

第4阶段——制作根文件系统之分析init进程(2)

本节目标: (1) 了解busybox(init进程和命令都放在busybox中) (2) 创建SI工程,分析busybox源码来知道init进程做了哪些事情 ...

25790
来自专栏专栏

跨环境测试框架介绍-pytest的高级用法

本文将介绍针对测试和生产等不同测试环境下,维护一套可读性,追溯性强的测试用例的工具-pytest。

59140
来自专栏程序员阿凯

一条大河波浪宽 -- 数据库连接池实现

11940
来自专栏Java Web

初学Java Web(5)——cookie-session学习

HTTP 协议 Web 浏览器与 Web 服务器之间的一问一答的交互过程必须遵守一定的规则,这样的规则就是 HTTP 协议。 HTTP 是 hypertext ...

31950
来自专栏有趣的django

10.Flask上下文

 应用上下文和请求上下文都是存放在一个‘LocalStack’的栈中,和应用app相关的操作就必须要用到应用上下文,比如通过current_app获取当前的这个...

9110

扫码关注云+社区

领取腾讯云代金券