(三)创建功能模块

来源:互联网 发布:深圳淘宝免费运营 编辑:程序博客网 时间:2024/06/06 03:37

原文链接:https://angular-2-training-book.rangle.io/handout/modules/feature-modules.html

当我们的根模块开始增长时,最开始显而易见的是,某些元素(组件,指令等)在某方面相关联,几乎感觉他们属于可以被“插入”的库。

在我们前面的例子中,我们开始看到了。 我们的根模块具有组件,管道和服务,其唯一目的是处理信用卡。 如果我们将这三个元素提取到自己的特征模块 ,然后将其导入到我们的根模块会怎么样?

我们要做到这一点。 第一步是创建两个文件夹,以将属于根模块的元素与属于功能模块的元素区分开来。

.├── app│   ├── app.component.ts│   └── app.module.ts├── credit-card│   ├── credit-card-mask.pipe.ts│   ├── credit-card.component.ts│   ├── credit-card.module.ts│   └── credit-card.service.ts├── index.html└── main.ts

请注意每个文件夹如何拥有自己的模块文件: app.module.ts和credit-card.module.ts 。 我们首先关注后者。

credit-card/credit-card.module.ts

import { NgModule } from '@angular/core';import { CommonModule } from '@angular/common';import { CreditCardMaskPipe } from './credit-card-mask.pipe';import { CreditCardService } from './credit-card.service';import { CreditCardComponent } from './credit-card.component';@NgModule({  imports: [CommonModule],  declarations: [    CreditCardMaskPipe,    CreditCardComponent  ],  providers: [CreditCardService],  exports: [CreditCardComponent]})export class CreditCardModule {}

我们的功能CreditCardModule它非常类似于根模块AppModule,但还是有一些重要的区别:

*我们没有导入BrowserModule而是CommonModule 。 如果我们在这里可以看到BrowserModule的文档,我们可以看到它是通过许多其他服务重新导出CommonModule ,有助于在浏览器中渲染Angular应用程序。 这些服务将我们的根模块与特定平台(浏览器)相结合,但我们希望我们的功能模块是独立于平台的。 这就是为什么我们只在那里导入CommonModule ,它只导出通用的指令和管道。

当涉及组件,管道和指令时,如果在根模块或任何其他功能模块中导入相同的依赖项,则每个模块都应导入自己的依赖项。 简而言之,即使有多个功能模块,每个功能模块都需要导入CommonModule 。 我们正在使用一种名为exports的新属性。 默认情况下 , declarations数组中定义的每个元素都是私有的 。 我们应该只输出我们应用程序中执行其他任务的其他模块。 在我们的例子中,我们只需要使CreditCardComponent可见,因为它被用在AppComponent的模板中。 

app/app.component.ts

...@Component({  ...  template: `    ...    <app-credit-card></app-credit-card>  `})export class AppComponent {}
我们正在将CreditCardMaskPipe保留为私有,因为它仅在CreditCardModule中使用 ,没有被其他模块直接使用。

我们现在可以将此功能模块导入到我们简化的根模块中。

app/app.module.ts

import { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { CreditCardModule } from '../credit-card/credit-card.module';import { AppComponent } from './app.component';@NgModule({  imports: [    BrowserModule,    CreditCardModule  ],  declarations: [AppComponent],  bootstrap: [AppComponent]})export class AppModule { }

到这一步,我们完成了,我们的应用程序如预期的一样。

查看示例

服务和延迟加载模块

这是Angular模块的棘手部分。 除非明确导出组件,管道和指令的范围,否则服务是全局可用的,除非模块被懒加载。

很难理解,我们首先让我们尝试在我们的示例中看到CreditCardService发生了什么。 请注意,该服务不在exports数组中,而是在providers数组中。 通过这种配置,即使在另一个模块中的AppComponent中,我们的服务也将随处可见。 所以,即使使用模块,也没有办法拥有一个“私有”服务,除非这个模块被懒加载。

当模块被懒惰加载时,Angular将创建一个子注入器(它是根模块的根注射器的子代),并且将在那里创建我们的服务实例。

想象一下,我们的CreditCardModule被配置为懒惰加载。 使用我们当前的配置,当应用程序被引导并且我们的根模块加载到内存中时,将会将CreditCardService (单例)的实例添加到根注入器中。 但是,当CreditCardModule在将来某个时候被懒惰加载时,将为该模块创建一个新的CreditCardService 实例的子注入器。 在这一点上,我们有一个分层注入器,具有相同服务的两个实例 ,这通常不是我们想要的。

想想一个执行身份验证的服务的示例。 我们希望在整个应用程序中只有一个单例,不管我们的模块是在bootstrap还是lazy加载的时候加载。 因此,为了使我们的功能模块的服务只添加到根注射器,我们需要使用不同的方法。

credit-card/credit-card.module.ts

import { NgModule, ModuleWithProviders } from '@angular/core';/* ...other imports... */@NgModule({  imports: [CommonModule],  declarations: [    CreditCardMaskPipe,    CreditCardComponent  ],  exports: [CreditCardComponent]})export class CreditCardModule {  static forRoot(): ModuleWithProviders {    return {      ngModule: CreditCardModule,      providers: [CreditCardService]    }  }}

不同于以前,我们并没有把我们的服务直接放在NgModule装饰器的providers属性中 。 这一次我们定义了一个名为forRoot的静态方法,通过它定义要导出的模块和服务。

使用这种新语法,我们的根模块略有不同。

app/app.module.ts

/* ...imports... */@NgModule({  imports: [    BrowserModule,    CreditCardModule.forRoot()  ],  declarations: [AppComponent],  bootstrap: [AppComponent]})export class AppModule { }

你能发现差异吗? 我们不直接导入CreditCardModule ,而是导入从forRoot方法返回的对象,其中包括CreditCardService 。 虽然这种语法比原始语法更复杂一些,但它将保证我们只有一个CreditCardService实例被添加到根模块中。 当加载CreditCardModule (甚至延迟加载)时,不会将该服务的新实例添加到子注入器。

查看示例

作为经验法则, 在从功能模块导出服务时始终使用forRoot语法 ,除非您有非常特殊的需求,需要在依赖注入树的不同级别进行多个实例。

原创粉丝点击