引言
angular2相比1引入了更完善的模块系统,回忆ng1的应用中通常在页面的html标签或body标签中添加ng-app节点,值为应用的模块名,整个应用都将围绕这个模块来展开,到了ng2,模块概念完善了很多,并且再不是由一个模块来统治整个应用(当然非要这么做也可以),本文就将叙述一番ng2中的模块体系,以及如何统筹ng2模块最终组合成一个完整的应用。
一、根模块、子模块与惰性加载
先说根模块。一个ng2应用至少要有一个根模块,包含ng2自带的BrowserModule,并声明为引导模块,在应用启动时将从此模块展开。
随着应用的扩大,所有的事情都在一个模块中完成难免会变乱(某种程度上看ng1应用就是这么做的,并且细分了控制器来拆分应用,这其实浪费了最顶层模块的意义),所以自然而然能想到,可以将系统分为多个模块,每个模块都只做各自的事情而互不干扰,所以进一步的思路就是,用来根模块来引导程序并管理所有子模块(通过路由定向以及为它们提供全局配置与服务实例),所有的具体业务就交给各个子模块来完成。
然后会有一个问题,那就是既然系统已经被这么多个子模块瓜分了,并且这些子模块也不可能同时全部都会被使用,也就是只有其被激活时才有用,那仍然在应用引导时从根模块加载所有子模块必然会导致性能的浪费以及拖慢执行速度。此时惰性加载模块就派上用场了,将一个子模块定义为惰性加载后,只有在通过路由激活此模块时才会开始加载此模块(并且ng2甚至支持异步预加载,即后台预加载懒加载的模块,这样当懒加载模块需要被加载时其实其已经加载完成了,又加快了响应速度)。
二、除了模块之外
每个模块都有自己的事情要做,通常包括:
- 引入其他模块 这个在第三部分细说
- 声明模块包含的组件、指令与管道 所有的组件、指令或管道都必须依附于某个模块,并只在此模块中可用。
- 定义模块提供的服务 服务也有自己所属的模块,但由于服务是全局单例的,所以只要在一个模块中提供之后,全局都通用。注:若多个模块同时提供了服务(通常发生在模块间混乱的import时),一般情况下ng2能识别并只保留一个实例,但在惰性加载的模块中则会发生不可预计的错误,所以一定要避免。
- 定义模块将导出的组件、指令与管道(还可以是此模块引入的模块) 与(1配合使用,同样在第三部分细说。
三、模块的关联
模块之间一定要有共享或继承资源的方式,不然的话每个子模块都必须实现可能用到的全部功能。
比如一个消息弹窗组件,不可能每个子模块都自己声明一个消息组件然后使用,这样的维护压力很大且代码严重浪费。
此时就用到了模块的引入和导出——
模块A可以引入另一个模块B,然后A就可以使用B中导出的组件、管道和指令。
当我们要使用系统指令(如ngIf、ngFor等)时,也必须引入系统模块,有一个巧妙的办法就是实现上图这样的共享模块,在引入系统模块并导出的同时再导出自定义的其他指令、组件或管道。然后所有引入了此共享模块的子模块就能使用这些系统指令和共享指令了。
有一个基础的问题是服务需不需要导出,答案是否定的。服务不需要导出,因为服务是全局单例的,一旦被初始化,就已经全局通用了,相反如果重复的导入提供了同一服务的模块就可能发生问题:
B提供服务B_S,A导入了B,C也导入了B。这种情况下ng2会找到两处B_S的提供,但ng2尚能够将其保持在一个实例B_S。但若模块C为惰性加载的模块,在C被创建时,其实会重新初始化一个实例B_S,从C跳转回到A时,又会创建一个B_S,来回每次跳转都是如此,结局就会变得混乱不堪。
对于服务更好的做法是写一个核心模块,专门提供全局服务,保证此模块只会被根模块引用一次,然后所有的子模块就都已经可以使用这些全局服务了。
四、ng2模块体系
最后给出一套ng2项目构建的体系,这也是总结归纳了ng2官网的推荐得出的ng2项目的模块体系。
总体思路就是:
> 1.根模块负责全局的路由。
> 2.核心模块负责全局服务,也可以定义一些只在根模块中使用的组件等,并只能由根模块引入一次,不再导出。
> 3.共享模块不做服务的提供,而是定义全局共享的组件等,以及帮助子模块导入系统模块,让子模块只需要导入此共享模块就够了。
> 4.子模块内部可以细分自己的子路由到具体的子组件,以及提供自己的服务等。
> 5.除了页面入口模块(即除了根模块外的具体业务模块)之外的其他子模块均考虑写成惰性加载的模块,以提升页面引导的速度减少性能浪费。
> 6.当需要一个比较通用的全局服务时,可以将其加入CoreModule,也可以再创建一个仅被根模块引入的特性模块。进一步的,甚至可以将此模块发布到npm,这就需要更强的编码能力和技术积累了。