372 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			372 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| ---
 | ||
| title: NgModules
 | ||
| localeTitle: iModules
 | ||
| ---
 | ||
| # NgModules
 | ||
| 
 | ||
| #### 动机
 | ||
| 
 | ||
| 角度应用程序从根NgModule开始。 Angular通过其由NgModules组成的模块系统来管理应用程序的依赖关系。除了普通的JavaScript模块,NgModules还可确保代码模块化和封装。
 | ||
| 
 | ||
| 模块还提供最高级别的组织代码。每个NgModule都以自己的代码块为根。该模块为其代码提供从上到下的封装。然后,整个代码块可以导出到任何其他模块。从这个意义上说,NgModules就像守门员一样对待自己的代码块。
 | ||
| 
 | ||
| Angular的文档实用程序来自Angular编写的NgModules。除非声明它的NgModule包含在根中,否则没有可用的实用程序。这些实用程序还必须从其主机模块导出,以便导入程序可以使用它们。这种封装形式使开发人员能够在同一文件系统中生成自己的NgModule。
 | ||
| 
 | ||
| 另外,了解为什么Angular CLI(命令行界面)从`@angular/core`导入`BrowserModule`是有意义的。只要使用CLI命令生成新应用程序,就会发生这种情况: `ng new [name-of-app]` 。
 | ||
| 
 | ||
| 在大多数情况下,理解实施的点可能就足够了。但是,了解实现如何将自身连接到根目录甚至更好。这一切都是通过将`BrowserModule`导入根目录而自动完成的。
 | ||
| 
 | ||
| #### NgModule装饰器
 | ||
| 
 | ||
| Angular通过修饰泛型类来定义其模块。 `@NgModule`装饰器指示类对Angular的模块化目的。 NgModule类合并了可从模块范围访问/实例化的根依赖项。 “范围”表示源自模块元数据的任何内容。
 | ||
| 
 | ||
| ```typescript
 | ||
| import { NgModule } from '@angular/core'; 
 | ||
|  
 | ||
|  @NgModule({ 
 | ||
|   // … metadata … 
 | ||
|  }) 
 | ||
|  export class AppModule { } 
 | ||
| ```
 | ||
| 
 | ||
| #### NgModule元数据
 | ||
| 
 | ||
| CLI生成的根NgModule包括以下元数据字段。这些字段为NgModule主持的代码块提供配置。
 | ||
| 
 | ||
| *   `declarations: []`
 | ||
| *   `imports: []`
 | ||
| *   `providers: []`
 | ||
| *   `bootstrap: []`
 | ||
| 
 | ||
| ##### 声明
 | ||
| 
 | ||
| 声明数组包括由NgModule托管的所有组件,指令或管道。除非在元数据中明确导出,否则它们对模块是私有的。鉴于此用例,组件,指令和管道被昵称为“声明”。 NgModule必须声明一个唯一的声明。声明不能在单独的NgModules中声明两次。否则会抛出错误。请参阅以下示例。
 | ||
| 
 | ||
| ```typescript
 | ||
| import { NgModule } from '@angular/core'; 
 | ||
|  import { TwoComponent } from './components/two.component.ts'; 
 | ||
|  
 | ||
|  @NgModule({ 
 | ||
|   declarations: [ TwoComponent ] 
 | ||
|  }) 
 | ||
|  export class TwoModule { } 
 | ||
|  
 | ||
|  @NgModule({ 
 | ||
|   imports: [ TwoModule ], 
 | ||
|   declarations: [ TwoComponent ] 
 | ||
|  }) 
 | ||
|  export class OneModule { } 
 | ||
| ```
 | ||
| 
 | ||
| Angular为了NgModule封装而抛出一个错误。声明是NgModule的私有,默认情况下声明它们。如果多个NgModule需要某个可声明的,它们应该导入声明的NgModule。然后,此NgModule必须导出所需的声明,以便其他NgModule可以使用它。
 | ||
| 
 | ||
| ```typescript
 | ||
| import { NgModule } from '@angular/core'; 
 | ||
|  import { TwoComponent } from './components/two.component.ts'; 
 | ||
|  
 | ||
|  @NgModule({ 
 | ||
|   declarations: [ TwoComponent ], 
 | ||
|   exports: [ TwoComponent ] 
 | ||
|  }) 
 | ||
|  export class TwoModule { } 
 | ||
|  
 | ||
|  @NgModule({ 
 | ||
|   imports: [ TwoModule ] // this module can now use TwoComponent 
 | ||
|  }) 
 | ||
|  export class OneModule { } 
 | ||
| ```
 | ||
| 
 | ||
| 上面的例子不会抛出错误。 TwoComponent已在两个NgModules之间唯一声明。 OneModule也可以访问TwoComponent,因为它导入了TwoModule。 TwoModule依次导出TwoComponent供外部使用。
 | ||
| 
 | ||
| ##### 进口
 | ||
| 
 | ||
| imports数组只接受NgModules。除了其他NgModules之外,此数组不接受声明,服务或其他任何内容。导入模块可以访问模块公布的可声明内容。
 | ||
| 
 | ||
| 这解释了为什么导入`BrowserModule`可以访问其各种实用程序。 `BrowserModule`声明的每个可声明实用程序都从其元数据导出。导入`BrowserModule` ,导出的NgModule可以使用这些导出的声明。服务根本不导出,因为它们缺少相同的封装。
 | ||
| 
 | ||
| ##### 供应商
 | ||
| 
 | ||
| 考虑到声明的封装,缺乏服务封装可能看起来很奇怪。请记住,服务与声明或导出分开,与提供者数组分开。
 | ||
| 
 | ||
| 当Angular编译时,它会使根NgModule变平并将其导入到一个模块中。服务组合在一起由合并的NgModule托管的单个提供者阵列中。声明通过一组编译时标志来维护它们的封装。
 | ||
| 
 | ||
| 如果NgModule提供程序包含匹配的标记值,则导入的根模块优先。过去,导入的最后一个NgModule优先。请参阅下一个示例。特别注意导入另外两个的NgModule。识别这会如何影响所提供服务的优先级。
 | ||
| 
 | ||
| ```typescript
 | ||
| import { NgModule } from '@angular/core'; 
 | ||
|  
 | ||
|  @NgModule({ 
 | ||
|   providers: [ AwesomeService ], // 1st precedence + importing module 
 | ||
|   imports: [ 
 | ||
|     BModule, 
 | ||
|     CModule 
 | ||
|   ] 
 | ||
|  }) 
 | ||
|  export class AModule { } 
 | ||
|  
 | ||
|  @NgModule({ 
 | ||
|   providers: [ AwesomeService ]  // 3rd precedence + first import 
 | ||
|  }) 
 | ||
|  export class BModule { } 
 | ||
|  
 | ||
|  @NgModule({ 
 | ||
|   providers: [ AwesomeService ]  // 2nd precedence + last import 
 | ||
|  }) 
 | ||
|  export class CModule { } 
 | ||
| ```
 | ||
| 
 | ||
| 从AModule的范围内实例化AwesomeService会产生AModule元数据中提供的AwesomeService实例。如果AModule的提供者省略了这项服务,那么CModule的AwesomeService将优先。如果CModule的提供者省略了AwesomeService,那么对于BModule来说等等。
 | ||
| 
 | ||
| ##### 引导
 | ||
| 
 | ||
| 引导程序阵列接受组件。对于Array的每个组件,Angular将组件作为其自己的`index.html`文件的根插入。 CLI生成的应用程序的根NgModule将始终具有此字段。
 | ||
| 
 | ||
| ```typescript
 | ||
| import { BrowserModule } from '@angular/platform-browser'; 
 | ||
|  import { NgModule } from '@angular/core'; 
 | ||
|  import { AppComponent } from './app.component'; 
 | ||
|  
 | ||
|  @NgModule({ 
 | ||
|   declarations: [ AppComponent ], 
 | ||
|   imports: [ BrowserModule ], 
 | ||
|   providers: [], 
 | ||
|   bootstrap: [ AppComponent ] 
 | ||
|  }) 
 | ||
|  export class AppModule { } 
 | ||
| ```
 | ||
| 
 | ||
| AppComponent的元素将注入应用程序的基本HTML( `index.html` )。组件树的其余部分从那里展开。总体NgModule的范围涵盖整个树以及从引导程序阵列注入的任何其他树。该数组通常只包含一个元素。这个组件将模块表示为单个元素及其底层树。
 | ||
| 
 | ||
| #### NgModules与JavaScript模块
 | ||
| 
 | ||
| 您已经在前面的示例中看到过Angular和JavaScript模块一起工作。最顶层的`import..from`语句构成了JavaScript模块系统。每个语句的目标的文件位置必须导出与请求匹配的类,变量或函数。 `import { TARGET } from './path/to/exported/target'` 。
 | ||
| 
 | ||
| 在JavaScript中,模块是文件分隔的。如前所述,使用`import..from`关键字导入文件。另一方面,NgModules是类分隔的,并用`@NgModule` 。因此,许多Angular模块可以存在于单个文件中。 JavaScript不会发生这种情况,因为文件定义了一个模块。
 | ||
| 
 | ||
| 约定,惯例说每个`@NgModule`装饰类应该有自己的文件。即便如此,要知道文件在Angular中不构成自己的模块。用`@NgModule`类创建了这种区别。
 | ||
| 
 | ||
| JavaScript模块提供对`@NgModule`元数据的标记引用。这发生在托管NgModule类的文件的顶部。 NgModule在其元数据字段(声明,导入,提供程序等)中使用这些标记。 `@NgModule`可以装饰一个类的唯一原因是JavaScript从文件的顶部导入它。
 | ||
| 
 | ||
| ```typescript
 | ||
| // JavaScript module system provides tokens 
 | ||
|  import { BrowserModule } from '@angular/platform-browser'; 
 | ||
|  import { NgModule } from '@angular/core'; 
 | ||
|  import { AppComponent } from './app.component'; 
 | ||
|  import { AppService } from './app.service'; 
 | ||
|  // Javascript module system is strict about where it imports. It can only import at the top of files. 
 | ||
|  
 | ||
|  // Angular NgModule uses those tokens in its metadata settings 
 | ||
|  @NgModule({ // import { NgModule } from '@angular/core'; 
 | ||
|   declarations: [ 
 | ||
|     AppComponent // import { AppComponent } from './app.component'; 
 | ||
|   ], 
 | ||
|   imports: [ 
 | ||
|     BrowserModule // import { BrowserModule } from '@angular/platform-browser'; 
 | ||
|   ], 
 | ||
|   providers: [ 
 | ||
|     AppService // import { AppService } from './app.service'; 
 | ||
|   ], 
 | ||
|   bootstrap: [ 
 | ||
|     AppComponent // import { AppComponent } from './app.component'; 
 | ||
|   ] 
 | ||
|  }) 
 | ||
|  export class AppModule { } 
 | ||
|  // JavaScript module system exports the class. Other modules can now import AppModule. 
 | ||
| ```
 | ||
| 
 | ||
| 上面的例子没有介绍任何新内容。这里的重点是解释两个模块化系统如何协同工作的评论。 JavaScript提供令牌引用,而NgModule使用这些令牌来封装和配置其底层代码块。
 | ||
| 
 | ||
| #### 功能模块
 | ||
| 
 | ||
| 应用程序增长超时。适当地扩展它们需要应用程序组织。一个坚实的系统将使进一步发展更容易。
 | ||
| 
 | ||
| 在Angular中,原理图确保目标驱动的代码段保持可区分。除了子NgModule原理图之外,还有NgModules本身。它们也是一种原理图。它们位于原理图列表中的其余部分,不包括应用程序本身。
 | ||
| 
 | ||
| 一旦应用程序开始扩展,根模块就不应该独立存在。功能模块包括与根NgModule一起使用的任何NgModule。您可以将根模块视为具有`bootstrap: []`元数据字段。功能应用程序确保根模块不会过度饱和其元数据。
 | ||
| 
 | ||
| 功能模块代表任何导入模块隔离一段代码。他们可以独立处理整个应用部分。这意味着它可以在根模块导入要素模块的任何应用程序中使用。这种策略可以节省开发人员在多个应用程序中的时间和精力!它还使应用程序的根NgModule保持精简状态。
 | ||
| 
 | ||
| 在应用程序的根NgModule中,将特征模块的标记添加到根的`imports`数组中就可以了。无论功能模块导出或提供的内容都可供根目录使用。
 | ||
| 
 | ||
| ```typescript
 | ||
| // ./awesome.module.ts 
 | ||
|  
 | ||
|  import { NgModule } from '@angular/core'; 
 | ||
|  import { AwesomePipe } from './awesome/pipes/awesome.pipe'; 
 | ||
|  import { AwesomeComponent } from './awesome/components/awesome.component'; 
 | ||
|  import { AwesomeDirective } from './awesome/directives/awesome.directive'; 
 | ||
|  
 | ||
|  @NgModule({ 
 | ||
|   exports: [ 
 | ||
|     AwesomePipe, 
 | ||
|     AwesomeComponent, 
 | ||
|     AwesomeDirective 
 | ||
|   ] 
 | ||
|   declarations: [ 
 | ||
|     AwesomePipe, 
 | ||
|     AwesomeComponent, 
 | ||
|     AwesomeDirective 
 | ||
|   ] 
 | ||
|  }) 
 | ||
|  export class AwesomeModule { } 
 | ||
| ```
 | ||
| 
 | ||
| ```typescript
 | ||
| // ./app.module.ts 
 | ||
|  
 | ||
|  import { AwesomeModule } from './awesome.module'; 
 | ||
|  import { BrowserModule } from '@angular/platform-browser'; 
 | ||
|  import { NgModule } from '@angular/core'; 
 | ||
|  import { AppComponent } from './app.component'; 
 | ||
|  
 | ||
|  @NgModule({ 
 | ||
|   declarations: [ 
 | ||
|     AppComponent 
 | ||
|   ], 
 | ||
|   imports: [ 
 | ||
|     AwesomeModule, 
 | ||
|     BrowserModule 
 | ||
|   ], 
 | ||
|   providers: [], 
 | ||
|   bootstrap: [ 
 | ||
|     AppComponent 
 | ||
|   ] 
 | ||
|  }) 
 | ||
|  export class AppModule { } 
 | ||
| ```
 | ||
| 
 | ||
| ```typescript
 | ||
| // ./app.component.ts 
 | ||
|  
 | ||
|  import { Component } from '@angular/core'; 
 | ||
|  
 | ||
|  @Component({ 
 | ||
|   selector: 'app-root', 
 | ||
|   template: ` 
 | ||
|   <!-- AwesomeDirective --> 
 | ||
|   <h1 appAwesome>This element mutates as per the directive logic of appAwesome.</h1> 
 | ||
|  
 | ||
|   <!-- AwesomePipe --> 
 | ||
|   <p>Generic output: {{ componentData | awesome }}</p> 
 | ||
|  
 | ||
|   <section> 
 | ||
|     <!-- AwesomeComponent --> 
 | ||
|     <app-awesome></app-awesome> 
 | ||
|   </section> 
 | ||
|   ` 
 | ||
|  }) 
 | ||
|  export class AppComponent { 
 | ||
|   componentData: string = "Lots of transformable data!"; 
 | ||
|  } 
 | ||
| ```
 | ||
| 
 | ||
| `<app-awesome></app-awesome>` (组件), `awesome` (管道)和`appAwesome` (指令)是AwesomeModule独有的。如果没有导出这些声明或AppModule忽略将AwesomeModule添加到其导入中,则AppComponent的模板将无法使用AwesomeModule的声明。 AwesomeModule是根NgModule AppModule的功能模块。
 | ||
| 
 | ||
| Angular提供了一些自己的模块,可以在输入时补充root。这是因为这些功能模块导出了他们创建的内容。
 | ||
| 
 | ||
| #### 静态模块方法
 | ||
| 
 | ||
| 有时,模块提供了使用自定义配置对象配置的选项。这是通过利用模块类中的静态方法实现的。
 | ||
| 
 | ||
| 这种方法的一个示例是`RoutingModule` ,它直接在模块上提供`.forRoot(...)`方法。
 | ||
| 
 | ||
| 要定义自己的静态模块方法,请使用`static`关键字将其添加到模块类。返回类型必须是`ModuleWithProviders` 。
 | ||
| 
 | ||
| ```ts
 | ||
| // configureable.module.ts 
 | ||
|  
 | ||
|  import { AwesomeModule } from './awesome.module'; 
 | ||
|  import { ConfigureableService, CUSTOM_CONFIG_TOKEN, Config } from './configurable.service'; 
 | ||
|  import { BrowserModule } from '@angular/platform-browser'; 
 | ||
|  import { NgModule } from '@angular/core'; 
 | ||
|  
 | ||
|  
 | ||
|  @NgModule({ 
 | ||
|   imports: [ 
 | ||
|     AwesomeModule, 
 | ||
|     BrowserModule 
 | ||
|   ], 
 | ||
|   providers: [ 
 | ||
|     ConfigureableService 
 | ||
|   ] 
 | ||
|  }) 
 | ||
|  export class ConfigureableModule { 
 | ||
|   static forRoot(config: Config): ModuleWithProviders { 
 | ||
|     return { 
 | ||
|         ngModule: ConfigureableModule, 
 | ||
|         providers: [ 
 | ||
|             ConfigureableService, 
 | ||
|             { 
 | ||
|                 provide: CUSTOM_CONFIG_TOKEN, 
 | ||
|                 useValue: config 
 | ||
|             } 
 | ||
|         ] 
 | ||
|     }; 
 | ||
|   } 
 | ||
|  } 
 | ||
| ```
 | ||
| 
 | ||
| ```ts
 | ||
| // configureable.service.ts 
 | ||
|  
 | ||
|  import { Inject, Injectable, InjectionToken } from '@angular/core'; 
 | ||
|  
 | ||
|  export const CUSTOM_CONFIG_TOKEN: InjectionToken<string> = new InjectionToken('customConfig'); 
 | ||
|  
 | ||
|  export interface Config { 
 | ||
|   url: string 
 | ||
|  } 
 | ||
|  
 | ||
|  @Injectable() 
 | ||
|  export class ConfigureableService { 
 | ||
|   constructor( 
 | ||
|     @Inject(CUSTOM_CONFIG_TOKEN) private config: Config 
 | ||
|   ) 
 | ||
|  } 
 | ||
| ```
 | ||
| 
 | ||
| 请注意, `forRoot(...)`方法返回的对象几乎与`NgModule`配置相同。
 | ||
| 
 | ||
| `forRoot(...)`方法接受用户在导入模块时可以提供的自定义配置对象。
 | ||
| 
 | ||
| ```ts
 | ||
| imports: [ 
 | ||
|   ... 
 | ||
|   ConfigureableModule.forRoot({ url: 'http://localhost' }), 
 | ||
|   ... 
 | ||
|  ] 
 | ||
| ```
 | ||
| 
 | ||
| 然后使用名为`CUSTOM_CONFIG_TOKEN`的自定义`InjectionToken`提供`ConfigureableService`并将其注入`ConfigureableService` 。应使用`forRoot(...)`方法仅导入一次`ConfigureableModule` 。这为`CUSTOM_CONFIG_TOKEN`提供了自定义配置。所有其他模块都应该导入`ConfigureableModule`而不使用`forRoot(...)`方法。
 | ||
| 
 | ||
| #### 来自Angular的NgModule示例
 | ||
| 
 | ||
| Angular提供了各种可从`@angular`导入的模块。两个最常导入的模块是`CommonModule`和`HttpClientModule` 。
 | ||
| 
 | ||
| `CommonModule`实际上是`BrowserModule`一个子集。两者都提供对`*ngIf`和`*ngFor`结构指令的访问。 `BrowserModule`包含Web浏览器的特定于平台的安装。 `CommonModule`省略了此安装。 `BrowserModule`应该导入到Web应用程序的根NgModule中。 `CommonModule`为不需要平台安装的功能模块提供`*ngIf`和`*ngFor` 。
 | ||
| 
 | ||
| `HttpClientModule`提供`HttpClient`服务。请记住,服务位于`@NgModule`元数据的providers数组中。它们不可申报。在编译期间,每个NgModule都合并到一个模块中。与声明不同,服务不是封装的。它们都可以通过位于合并的NgModule旁边的根注入器实例化。
 | ||
| 
 | ||
| 回到关键点。与任何其他服务一样, `HttpClient`通过依赖注入(DI)通过其构造函数实例化为类。使用DI,根注入器将`HttpClient`的实例注入构造函数。此服务允许开发人员使用服务的实现发出HTTP请求。
 | ||
| 
 | ||
| `HttpClient`实现包括`HttpClientModule`提供程序数组。只要根NgModule导入`HttpClientModule` , `HttpClient`就会按照预期从根的范围内实例化。
 | ||
| 
 | ||
| #### 结论
 | ||
| 
 | ||
| 您可能已经利用了Angular的NgModules。 Angular使得将模块很容易地放入根NgModule的imports数组中。实用程序通常从导入的模块的元数据中导出。因此,为什么它的实用程序在根NgModule中输入后突然变得可用。
 | ||
| 
 | ||
| NgModules与普通的JavaScript模块紧密配合。一个提供令牌,而一个使用它们进行配置。他们的团队合作产生了Angular框架独有的强大的模块化系统。它提供了一个新的组织层,高于除应用程序之外的所有其他原理图。
 | ||
| 
 | ||
| 希望本文能够进一步加深您对NgModules的理解。对于一些更奇特的用例,Angular可以进一步利用这个系统。本文介绍了基础知识,以便您可以使用以下链接了解更多信息。
 | ||
| 
 | ||
| ## 来源
 | ||
| 
 | ||
| *   [角度团队。 “NgModules”。 _谷歌_ 2018年6月6日访问。](https://angular.io/guide/ngmodules)
 | ||
| *   [Koretskyi,Maxim。 “避免与Angular模块的常见混淆”。 _Angular In Depth_ ,2017年8月10日。访问时间为2018年6月6日。](https://blog.angularindepth.com/avoiding-common-confusions-with-modules-in-angular-ada070e6891f)
 | ||
| 
 | ||
| ## 资源
 | ||
| 
 | ||
| *   [角度文档](https://angular.io/guide)
 | ||
| *   [角度深度](https://blog.angularindepth.com)
 | ||
| *   [Angular GitHub存储库](https://github.com/angular/angular)
 | ||
| *   [角度CLI](https://cli.angular.io) |