BrowserModule,CommonModule的选择:
BrowserModule提供了启动和运行浏览器应用的那些基本的服务提供商。BrowserModule还从@angular/common中重新导出了CommonModule,这意味着AppModule中的组件也同样可以访问那些每个应用都需要的Angular指令,如NgIf和NgFor。
在其它任何模块中都不要导入BrowserModule。特性模块和惰性加载模块应该改成导入CommonModule。它们需要通用的指令。它们不需要重新初始化全应用级的提供商。*如果你在惰性加载模块中导入BrowserModule,Angular就会抛出一个错误。*
特性模块中导入CommonModule可以让它能用在任何目标平台上,不仅是浏览器。那些跨平台库的作者应该喜欢这种方式的。
多次导入同一个模块的情况:
当三个模块全部都导入模块A时,Angular只会首次遇到时加载一次模块A,之后就不会这么做了。
Angular不允许模块之间出现循环依赖,所以不要让模块A导入模块B,而模块B又导入模块A。
为什么在惰性加载模块中声明的服务提供商只对该模块自身可见?
和启动时就加载的模块中得提供商不同,惰性加载模块中的提供商是局限于模块的。
当Angular路由器惰性加载一个模块时, 它创建了一个新的运行环境。那个环境拥有自己得注入器。它是应用注入器的直属子集。路由器把该惰性加载模块的提供商和它导入的模块的提供商添加到这个子注入器中。这些提供商不会被拥有相同令牌的应用级别提供商的变化所影响。当路由器在惰性加载环境中创建组件时,Angular优先使用惰性加载模块中得服务实力,而不是来自应用的根注入器的。
如果两个模块提供了同一个服务会怎么样?
当同时加载了两个导入的模块,它们都列出了使用同一个令牌的提供商时,后导入的模块会获胜,这是因为这两个提供商都被添加到了同一个注入器中。由根AppModule提供的服务相对于所导入模块中提供得服务有优先权。换句话说:AppModule总会获胜。
为什么SharedModule为惰性加载模块提供服务是个馊主意?
当路由器准备惰性加载Module的时候,它会创建一个子注入器,并且把service的提供商注册到那个子注入器中。子注入器和根注入器是不同的。
当Angular创一个惰性加载的组件时,它必须注入一个服务。这次,它会从惰性加载模块的子注入器重查找服务的提供商,并用它创建一个服务的新实例。这个服务实例与Angular在主动加载的组件中注入的全应用级单例对象截然不同。
这绝对是一个错误。
为什么惰性加载模块会创建一个子注入器?
Angular会把@NgModule.providers中提供商添加到应用的根注入器中...除非该模块是惰性加载的,这种情况下,它会创建一子注入器。并且把该模块的提供商添加到这个子注入器中。
归根结底,这来自于Angular依赖注入系统的一个基本特征:在注入器还没有被第一次使用之前,可以不断为其添加提供商。一旦注入器已经创建和开始交付服务,它得提供商列表就被冻结了,不再接受新的提供商。
当应用启动时,Angular会首先使用所有主动加载模块中的提供商来配置根注入器,这发生在它创建第一个组件以及注入任何服务之前。一旦应用开始工作,应用的根注入器就不再接受新的提供商了。
之后,应用逻辑开始惰性加载某个模块。Angular必须把这个惰性加载模块中的提供商添加到某个注入器中。但是它无法将它们添加到应用的根注入器中,因为根注入器已经不再接受新的提供商了。于是,Angular在惰性加载模块的上下文中创建了一个新的子注入器。
要如何知道一个模块或服务是否已经加载过了?
某些模块及其服务只能被根模块AppModule加载一次。在惰性加载模块中再次导入这个模块会导致错误的行为,这个错误可能非常难于检测和诊断。
为了防范这种风险,我们可以写一个构造函数,它会尝试从应用的根注入器中注入该模块或服务。如果这种注入成功了,那就说明这个类是被第二次加载的,我们就可以抛出一个错误,或者采取其他挽救措施。
constructor (@Optional() @SkipSelf() parentModule: CoreModule) { if (parentModule) { throw new Error( 'CoreModule is already loaded. Import it in the AppModule only'); } }