Fork me on GitHub
tc9011's

Angular官方文档学习笔记之Angular模块

2017030799173banner-angularjs.jpg

20170314148949839797878.png

Angular模块化

Angular 模块是一个由@NgModule装饰器提供元数据的类,元数据包括:

  • 声明哪些组件、指令、管道属于该模块。
  • 公开某些类,以便其它的组件模板可以使用它们。
  • 导入其它模块,从其它模块中获得模块所需的组件、指令和管道。
  • 在应用程序级提供服务,以便应用中的任何组件都能使用它。

应用的根模块

BrowserModule注册了一些关键的应用服务提供商。 它还包括了一些通用的指令,例如NgIfNgFor,所以这些指令在该模块的任何组件模板中都是可用的。

@NgModule.bootstrap属性把这个AppComponent标记为引导 (bootstrap) 组件。 当 Angular 引导应用时,它会在 DOM 中渲染AppComponent,并把结果放进index.html<my-app>元素标记内部。

引导根模块

通过即时 (JiT) 编译器动态引导

什么是Angular编译器?

Angular编译器会把我们所写的应用代码转换成高性能的JavaScript代码。 在编译过程中,@NgModule的元数据扮演了很重要的角色。

@NgModule元数据告诉Angular编译器要为当前模块编译哪些组件,以及如何把当前模块和其它模块链接起来。

Angular 编译器在浏览器中编译并引导该应用:

1
2
3
4
5
6
7
8
// The browser platform with a compiler
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
// The app module
import { AppModule } from './app/app.module';
// 编译并运行模块
platformBrowserDynamic().bootstrapModule(AppModule);

使用预编译器 (AoT) 进行静态引导

静态方案可以生成更小、启动更快的应用,建议优先使用它,特别是在移动设备或高延迟网络下。

使用静态选项,Angular 编译器作为构建流程的一部分提前运行,生成一组类工厂。它们的核心就是AppModuleNgFactory

引导预编译的AppModuleNgFactory的语法和动态引导AppModule类的方式很相似。

1
2
3
4
5
6
7
8
// The browser platform without a compiler
import { platformBrowser } from '@angular/platform-browser';
// The app module factory produced by the static offline compiler
import { AppModuleNgFactory } from './app/app.module.ngfactory';
// Launch with the app module factory.
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);

由于整个应用都是预编译的,所以我们不用把 Angular 编译器一起发到浏览器中,也不用在浏览器中进行编译。

下载到浏览器中的应用代码比动态版本要小得多,并且能立即执行。引导的性能可以得到显著提升。

无论是 JiT 还是 AoT 编译器都会从同一份AppModule源码中生成一个AppModuleNgFactory类。 JiT 编译器动态地在浏览器的内存中创建这个工厂类AoT 编译器把工厂输出成一个物理文件,也就是我们在静态版本main.ts中导入的那个。

声明

自定义的组件和指令声明在模块的declarations中。

导入

BrowserModule导入了CommonModule并且重新导出了它。 最终的效果是:只要导入BrowserModule就自动获得了CommonModule中的指令。

模块可以重新导出其它模块,这会导致重新导出它们导出的所有类。 纯服务类的模块不会导出任何可供其它模块使用的可声明类,例如HttpModule

组件、指令和管道只能属于一个模块。永远不要再次声明属于其它模块的类。

如果有两个同名指令,只要在 import 时使用as关键字来为第二个指令创建个别名就可以了。

1
2
3
import {
HighlightDirective as ContactHighlightDirective
} from './contact/highlight.directive';

特性模块

特性模块

我们引导根模块来启动应用,但导入特性模块来扩展应用。特性模块可以对其它模块暴露或隐藏自己的实现。

几乎所有要在浏览器中使用的应用的根模块AppModule)都应该从@angular/platform-browser中导入BrowserModule

添加 ContactModule

BrowserModule还从@angular/common中重新导出了CommonModule,这意味着AppModule中的组件也同样可以访问那些每个应用都需要的Angular指令,如NgIfNgFor

在其它任何模块中都不要导入BrowserModule特性模块惰性加载模块应该改成导入CommonModule。 它们需要通用的指令。它们不需要重新初始化全应用级的提供商。

通过路由器惰性加载模块

应用路由

惰性加载模块的位置是字符串而不是类型。 在本应用中,该字符串同时标记出了模块文件和模块,两者用#分隔开。

1
2
{ path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' },
{ path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule' }

路由到特性模块

当需要为根模块和特性模块分别提供不同的导入值时,forRootforChild是约定俗成的方法名。

共享模块

通过让SharedModule重新导出CommonModuleFormsModule模块,可以消除CommonModuleFormsModule模块重复导入。

不要在共享模块中把应用级单例添加到providers中。 否则如果一个惰性加载模块导入了此共享模块,就会导致它自己也生成一份此服务的实例。

核心模块

把这些一次性的类收集到CoreModule中,并且隐藏它们的实现细节。 简化之后的根模块AppModule导入CoreModule来获取其能力。

用forRoot配置核心服务

模块的静态方法forRoot可以同时提供并配置服务。 它接收一个服务配置对象,并返回一个ModuleWithProviders。这个简单对象具有两个属性:

  • ngModule - CoreModule
  • providers - 配置好的服务提供商

根模块AppModule会导入CoreModule类并把它的providers添加到AppModule的服务提供商中。

一个可选的、被注入的UserServiceConfig服务扩展核心的UserService服务。 如果有UserServiceConfigUserService就会据此设置用户名。

1
2
3
4
5
src/app/core/user.service.ts (constructor)
constructor(@Optional() config: UserServiceConfig) {
if (config) { this._userName = config.userName; }
}

CoreModule.forRoot接收UserServiceConfig对象:

1
2
3
4
5
6
7
8
9
10
src/app/core/core.module.ts (forRoot)
static forRoot(config: UserServiceConfig): ModuleWithProviders {
return {
ngModule: CoreModule,
providers: [
{provide: UserServiceConfig, useValue: config }
]
};
}

AppModuleimports列表中调用它:

1
2
3
4
5
6
imports: [
BrowserModule,
ContactModule,
CoreModule.forRoot({userName: 'Miss Marple'}),
AppRoutingModule
],

只在应用的根模块AppModule中调用forRoot。 如果在其它模块(特别是惰性加载模块)中调用它则违反了设计意图,并会导致运行时错误。

别忘了导入其返回结果,而且不要把它添加到@NgModule的其它任何列表中。

禁止重复导入CoreModule

只有根模块AppModule才能导入CoreModule。 如果惰性加载模块导入了它,就会出问题

如果我们错误的把CoreModule导入了一个惰性加载模块, @SkipSelf让 Angular 在其父注入器中查找CoreModule,这次,它的父注入器却是根注入器了。 当然,这次它找到了由根模块AppModule导入的实例。 该构造函数检测到存在parentModule,于是抛出一个错误。

1
2
3
4
5
6
constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error(
'CoreModule is already loaded. Import it in the AppModule only');
}
}
tc9011 wechat
欢迎订阅我的微信公众号
坚持原创技术分享,您的支持将鼓励我继续创作!
------ 本文结束 ------

版权声明

tc9011's Notes by Cheng Tang is licensed under a Creative Commons BY-NC-ND 4.0 International License.
汤诚创作并维护的tc9011's Notes博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证
本文首发于tc9011's Notes 博客( http://tc9011.com ),版权所有,侵权必究。