NgModule 是你使用 Angular 编写应用程序时遇到的第一个基本结构,但因为涉及不一样的做用域范围,它也是最微妙和最复杂的。若是你想详细了解 NgModule 的相关知识,能够直接参考 Angualr NgModule FAQ 或 Angular FAQ 文章中 NgModule 版块的内容 。html
咱们可使用 Angular CLI,自动完成不少工做,但咱们必需要作的第一件事就是加载根模块。git
platformBrowserDynamic().bootstrapModule(AppModule);
NgModule 的目的是声明咱们在 Angular 模块中建立的内容,主要有如下两种结构:github
declarations - 声明模板中使用的内容,大部分是组件,也包括指令和管道。typescript
providers - 用于声明服务。bootstrap
import { NgModule } from '@angular/core'; import { SomeComponent } from './some.component'; import { SomeDirective } from './some.directive'; import { SomePipe } from './some.pipe'; import { SomeService } from './shared/some.service'; @NgModule({ declarations: [SomeComponent, SomeDirective, SomePipe], providers: [SomeService] }) export class SomeModule {}
NgModule 是 Angular RC 阶段新增的一个功能,由于 ES 6 中已经新增 import/export 功能,在引入 NgModule 彷佛增长了没必要要的复杂性。但引入了 NgModule 让咱们可使用 AOT
编译,这大大提升了应用的性能。在 Angular 2 早期 Beta 版本中,咱们的组件在使用指令时,都必须每次导入相应的指令,这大大增长开发成本。框架
declarations
和 providers
属性最使人困惑的是,它们没有相同的做用域和可见性 (scope / visibility):ide
declarations / components 是本地做用域 (private visibility)性能
providers / services 是全局做用域 (public visibility)flex
这意味着你声明的组件只能在当前模块中使用。若是你想要在外面使用声明的组件,你必须导出它们:code
import { NgModule } from '@angular/core'; import { SomeComponent } from './some.component'; import { SomeDirective } from './some.directive'; import { SomePipe } from './some.pipe'; @NgModule({ declarations: [SomeComponent, SomeDirective, SomePipe], exports: [SomeComponent, SomeDirective, SomePipe] }) export class SomeModule {}
反之,模块中的声明的服务,咱们能够在全部模块中使用。
components 和 services 拥有不一样的做用域,了解这个区别很重要。若是咱们的应用程序不只仅包含一个模块,事情可能会变得很糟糕。Angular 框架内部也拆分红多个不一样的模块,如 core、common、http 等等。
在 Angular 模块中咱们须要作的一件主要的事情是导入其它模块:
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { HttpModule } from '@angular/http'; import { FeatureModule } from '../feature/feature.module'; @NgModule({ imports: [CommonModule, HttpModule, FeatureModule] }) export class SomeModule {}
如今的主要问题是,你必须知道为何须要导入这些模块:
为了使用导入模块中声明的组件、指令或管道?
为了使用模块中定义的服务?
由于组件和服务,拥有不一样的做用域:
若是咱们须要在模块中使用导入模块中声明的组件,那咱们须要在每一个使用的模块中导入对应的模块
若是咱们只是使用模块中定义的服务,那咱们只须要在主模块中导入对应的模块
若是你不了解这些区别,你可能因为忘记导入某个模块,而出现组件不可用的错误。或者你为了使用某个模块中定义的服务,而屡次导入同一个模块。
CommonModule (包含 Angular 中定义的内建指令,如 ngIf、ngFor 等),除了在主模块以外,不须要导入,由于咱们已经在主模块中导入了 BrowserModule
(此模块已导入了 CommonModule)。其它模块都必须手动导入该模块。
FormsModule / ReactiveFormsModule
BrowserAnimationsModule
FlexLayoutModule
MaterialModule 和 UI Modules (如 PrimeNg)
Other Modules (定义 components、directives 或 pipes)
HttpModule
Other Modules (仅提供服务)
若使用 Angular CLI 建立新的模块,它会自动帮咱们导入 CommonModule
。若是你须要使用与组件相关的模块如 animations 、flex layout 或 Material 模块,你必须每次手动导入上述模块。所以一个好的实践是建立 SharedModule 模块。
管理 SharedModule 时,须要很当心。若是你只是导入模块,它将没法正常工做:
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FlexLayoutModule } from '@angular/flex-layout'; import { MaterialModule } from '@angular/material'; @NgModule({ imports: [CommonModule, FlexLayoutModule, MaterialModule] }) export class SharedModule {}
为何呢?缘由还是做用域的问题:组件只在 SharedModule 中可用,在其它导入 SharedModule 的模块中,仍然是不可用的。所以咱们须要导出相应的组件:
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FlexLayoutModule } from '@angular/flex-layout'; import { MaterialModule } from '@angular/material'; @NgModule({ imports: [CommonModule, FlexLayoutModule, MaterialModule], exports: [CommonModule, FlexLayoutModule, MaterialModule] }) export class SharedModule {}
若是咱们组件不包含其它共享的资源,咱们能够省略 imports 属性:
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FlexLayoutModule } from '@angular/flex-layout'; import { MaterialModule } from '@angular/material'; @NgModule({ exports: [CommonModule, FlexLayoutModule, MaterialModule] }) export class SharedModule {}
如何同时管理具备组件和服务的模块?这是一个比较复杂的问题。你可能已经接触过 RouterModule,该模块不只提供了 <router-outlet>
、routerLink
指令,并且还提供了 ActivedRouter
服务 (用于获取 URL 参数)、Router
服务 (用于页面导航) 。
幸运的是,这个问题是由模块自己来解决。 Angular CLI 会为咱们自动生成路由文件,但你可能已经注意到,应用程序主模块的路由和子模块路由之间存在细微差异。
对于 AppModule,咱们这样使用:
RouterModule.forRoot(routes)
对于子模块,咱们这样使用:
RouterModule.forChild(routes)
为何呢?由于在 AppModule 中,forRoot() 方法会导入路由模块中的指令和服务。但对于子模块来讲,forChild() 方法仅会导入路由模块中定义的指令,而不会再次导入模块中定义的服务。
若是你须要实现模块懒加载,能够简单地使用 Angular CLI 来实现:
const routes: Routes = [ { path: 'admin', loadChildren: './admin/admin.module#AdminModule' } ];
由于它将是一个不一样的包和模块,默认状况下是按需加载,它不会包含在您的应用程序的全局范围内。
对于组件来讲,你不须要更改任何内容:就像在任何子模块中同样,你须要再次导入 CommonModule 或 SharedModule 模块。
但对于服务来讲,有一些区别:
你仍然能够访问应用程序中提供的服务 (如Http和已定义的服务)。
延迟加载模块中定义的服务,只能在延迟加载的模块中使用,应用程序的其它模块是没法使用的。