目录

  • 目录
  • 前言
  • Angular模块化
    • AppModule - 应用的根模块
    • 在maints中引导
    • 声明指令和组件
    • 服务提供商
    • 导入支持性模块
  • 解决指令冲突
  • 特性模块
  • 通过路由器惰性加载模块
    • 应用路由
    • RouterModuleforRoot
    • 路由到特性模块
    • 路由到惰性加载的模块
  • 共享模块
    • 重新导出其它模块
  • 核心模块
  • 用forRoot配置核心服务
    • 用CoreModuleforRoot配置核心服务
  • 禁止重复导入CoreModule
  • 参考

前言

  Angular模块是带有**@NgModule装饰器函数的类。@NgModule接收一个元数据对象,该对象告诉 Angular 如何编译和运行模块代码。 它标记出该模块拥有的组件、指令和管道, 并把它们的一部分公开出去,以便外部组件使用它们。 它可以向应用的依赖注入器中添加服务提供商。参考例子代码:https://angular.cn/docs/ts/latest/guide/ngmodule.html。

Angular模块化

  Angular模块把组件、指令和管道打包成内聚的功能块,每个模块聚焦于一个特性区域、业务领域、工作流或通用工具。
  Angular模块是一个由@NgModule装饰器提供元数据的类,元数据包括:
- 声明哪些组件、指令、管道属于该模块;
- 公开某些类,以便其它的组件模板可以使用它们;
- 导入其它模块,从其它模块中获得本模块所需的组件、指令和管道;
- 在应用程序级提供服务,以便应用中的任何组件都能使用它。
  每个Angular应用至少有一个模块类——根模块,我们将通过引导根模块来启动应用。
  对于组件很少的简单应用来说,只用一个根模块就足够了。随着应用规模的增长,我们逐步从根模块中重构出一些特性模块,来代表一组相关功能的集合。然后,我们在根模块中导入它们。

AppModule - 应用的根模块

  每个Angular应用都有一个根模块类。
  @NgModule装饰器用来为模块定义元数据。

在main.ts中引导

  在main.ts文件中,我们通过引导AppModule来启动应用。
  针对不同的平台,Angular提供了很多引导选项。
- 通过即时(JiT)编译器动态引导
- 使用预编译器(AoT - Ahead-Of-Time)进行静态引导

声明指令和组件

服务提供商

  模块可以往应用的“根依赖入器”中添加提供商,让那些服务在应用中到处可用。

导入支持性模块

  Angular能识别NgIf、NgFor等指令的原因,是因为在AppModule中我们导入了browserModule模块。
  导入browserModule会让该模块公开的所有组件、指令和管道在AppModule下的任何组件模板中直接可用,而不需要额外的繁琐步骤。

  更准确地说,NgIf是来自@angular/commonCommonModule中声明的。
  CommonModule提供了很多应用程序中常用的指令,包括NgIf和NgFor等。
  browserModule导入了CommonModule并且重新导出了它。最终的效果是:只要导入browserModule就自动获得了CommonModule中的指令。

  有些熟悉的Angular指令并不属于CommonModule。例如,NgModelRouterLink分别属于Angular的FormsModule模块和RouterModule模块。在使用那些指令之前,我们也必须导入那些模块。

注:永远不要再次声明属于其它模块的类。

解决指令冲突

  指令冲突表现在指令同名但功能不同时导入根模块时的情况。即这些指令是不同的,只是恰好指令名称相同而已。
  我们可以通过创建特性模块来消除组件与指令的冲突。特性模块可以把来自一个模块中的声明和来自另一个的区隔开。

特性模块

  特性模块是带有@NgModule装饰器及其元数据的类,就像根模块一样。特性模块的元数据和根模块的元数据的属性是一样的。
  根模块和特性模块还共享着相同的执行环境。它们共享着同一个依赖注入器,这意味着某个模块中定义的服务在所有模块中也都能用。
它们在技术上有两个显著的不同点:
1. 我们引导根模块来启动应用,但导入特性模块来扩展应用。
2. 特性模块可以对其它模块暴露或隐藏自己的实现。
  特性模块用来提供了内聚的功能集合。聚焦于应用的某个业务邻域、用户工作流、某个基础设施(表单、HTTP、路由),或一组相关的工具集合。
  虽然这些都能在根模块中做,但特性模块可以帮助我们把应用切分成具有特定关注点和目标的不同区域。
  特性模块通过自己提供的服务和它决定对外共享的那些组件、指令、管道来与根模块等其它模块协同工作。

  browserModule提供了启动和运行浏览器应用的那些基本的服务提供商。
  browserModule还从@angular/common中重新导出了CommonModule,这意味着AppModule中的组件也同样可以访问那些每个应用都需要的Angular指令,如NgIfNgFor
注:在其它任何模块中都不要导入browserModule。特性模块和惰性加载模块应该改成导入CommonModule。它们不需要重新初始化全应用级的提供商。
  如果你在惰性加载模块中导入browserModule,Angular就会抛出一个错误。
  特性模块中导入CommonModule可以让它用在任何目标平台,不仅是浏览器。那些跨平台库的作者应该喜欢这种方式的。

通过路由器惰性加载模块

应用路由

app/app-routing.module.ts
copY CODE
import { NgModule }             from '@angular/core';
import { Routes,RouterModule } from '@angular/router';

export const routes: Routes = [
  { path: '',redirectTo: 'contact',pathMatch: 'full'},{ path: 'crisis',loadChildren: 'app/crisis/crisis.module#CrisisModule' },{ path: 'heroes',loadChildren: 'app/hero/hero.module#HeroModule' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],exports: [RouterModule]
})
export class AppRoutingModule {}

  其中,contact路由并不是在这里定义的。对于带有路由组件的特性模块,其标准做法就是让它们定义自己的路由。如,ContactModule特性模块,在其特性区中定义自己的路由文件contact.routing.ts。
  另外两个路由使用惰性加载语法来告诉路由器要到哪里去找这些模块。

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

  惰性加载模块的位置是字符串而不是类型。在本应用中该字符串同时标记出了模块文件和模块类,两者用#分隔开。
  对于这些惰性模块(这里指CrisisModule和HeroModule模块),并不需要在根模块AppModule中导入。它们将在用户导航到其中的某个路由时,被异步获取并加载。在AppModule根模块中需要导入ContactModule模块,以便在应用启动时加载它的路由和组件。

RouterModule.forRoot

  RouterModule类的forRoot静态方法和提供的配置,被添加到imports数组中,提供该模块的路由信息。

@NgModule({
  imports: [RouterModule.forRoot(routes)],exports: [RouterModule]
})
export class AppRoutingModule {}

  该方法返回的AppRoutingModule类是一个路由模块,它同时包含了RouterModule指令和用来生成配置好的Router的依赖注入提供商。
  这个AppRoutingModule仅用于应用的根模块。
注:永远不要在特性路由模块中调用RouterModule.forRoot!

  只能在应用的根模块AppModule中调用并导入.forRoot的结果。 在其它模块中导入它,特别是惰性加载模块中,是违反设计目标的并会导致一个运行时错误。

路由到特性模块

  app/contact目录中也有一个新文件contact-routing.module.ts。 它定义了我们前面提到过的联系人路由,并提供了ContactRoutingModule,就像这样:

@NgModule({
  imports: [RouterModule.forChild([
    { path: 'contact',component: ContactComponent }
  ])],exports: [RouterModule]
})
export class ContactRoutingModule {}

  这次我们要把路由列表传给RouterModule的forChild方法。 该方法会为特性模块生成另一种对象。
注:总是在特性路由模块中调用RouterModule.forChild。
  当我们只需要从路由器导航到某个特性模块中的某个组件时,我们就不需要公开它了。如,通过路由器导航到ContactComponent组件,contact.module.ts定义如下:

@NgModule({
  imports:      [ CommonModule,FormsModule,ContactRoutingModule ],declarations: [ ContactComponent,HighlightDirective,AwesomePipe ],providers:    [ ContactService ]
})
export class ContactModule { }

  现在我们通过路由器导航到ContactComponent,所以也就没有理由公开它了。它也不再需要选择器 (selector)。 也没有模板会再引用ContactComponent。它从 AppComponent 模板中彻底消失了。

路由到惰性加载的模块

  惰性加载的HeroModule和CrisisModule与其它特性模块遵循同样的规则。它们和主动加载的ContactModule看上去没有任何区别。
如HeroModule:

@NgModule({
  imports: [ CommonModule,HeroRoutingModule ],declarations: [
    HeroComponent,HeroDetailComponent,HeroListComponent,HighlightDirective
  ]
})
export class HeroModule { }

共享模块

  在多个特性模块中,可能存在公共的组件、指令和管道。我们可以添加SharedModule来存放这些公共组件、指令和管道,并且共享给那些需要它们的模块。
如,项目中公共的有AwesomePipe管道、HighlightDirective指令、CommonModule和FormsModule模块。可以定义SharedModule如下:

import { NgModule }            from '@angular/core';
import { CommonModule }        from '@angular/common';
import { FormsModule }         from '@angular/forms';
import { AwesomePipe }         from './awesome.pipe';
import { HighlightDirective }  from './highlight.directive';
@NgModule({
  imports:      [ CommonModule ],declarations: [ AwesomePipe,HighlightDirective ],exports:      [ AwesomePipe,CommonModule,FormsModule ]
})
export class SharedModule { }

值得注意的有:
- 它导入了CommonModule,这是因为它的组件需要这些公共指令。
- 正如我们所期待的,它声明并导出了工具性的管道、指令和组件类。
- 它重新导出了CommonModule和FormsModule

重新导出其它模块

  当回顾应用程序时,我们注意到很多需要SharedModule的组件也同时用到了来自CommonModule的NgIf和NgFor指令,并且还通过来自FormsModule的[(ngModel)]指令绑定到了组件的属性。那些声明这些组件的模块将不得不同时导入CommonModuleFormsModuleSharedModule
  通过让SharedModule重新导出CommonModule和FormsModule模块,我们可以消除这种重复。于是导入SharedModule的模块也同时免费获得了CommonModuleFormsModule
  如,导入SharedModule的AppModule根模块,在该根模块中声明的组件,如果使用到NgIf或NgFor指令就不需要在AppModule中再导入CommonModule模块,同理,如果在该根模块中声明的组件,如果使用到NgModel指令同样也不要在AppModule中导入FormsModule模块了。
  实际上,SharedModule本身所声明的组件没绑定过[(ngModel)],那么,严格来说SharedModule并不需要导入FormsModule。
  这时SharedModule仍然可以导出FormsModule,而不需要先把它列在imports中。
注:不要在共享模块中把应用级单例添加到providers中。 否则如果一个惰性加载模块导入了此共享模块,就会导致它自己也生成一份此服务的实例。

  对于如只在AppComponent中使用一次的组件,我们没必要共享它。如:TitleComponent只被AppComponent用一次,我们就没必要共享它,即不要在SharedModule中导出这样的组件。
  全应用级的单例服务,不应在共享模块的providers中。如:UserService是全应用级单例。 我们不希望每个模块都各自有它的实例。

核心模块

  现在,我们的根目录下只剩下UserService和TitleComponent这两个被根组件AppComponent用到的类没有清理了。 但正如我们已经解释过的,它们无法被包含在SharedModule中。
  不过,我们可以把它们收集到单独的CoreModule中,并且只在应用启动时导入它一次,而不会在其它地方导入它
如CoreModule:

import {
  ModuleWithProviders,NgModule,Optional,SkipSelf }       from '@angular/core';
import { CommonModule }      from '@angular/common';
import { TitleComponent }    from './title.component';
import { UserService }       from './user.service';
@NgModule({
  imports:      [ CommonModule ],declarations: [ TitleComponent ],exports:      [ TitleComponent ],providers:    [ UserService ]
})
export class CoreModule {
}

  因此我们建议把这些一次性的类收集到CoreModule中,并且隐藏它们的实现细节。 简化之后的根模块AppModule导入CoreModule来获取其能力。记住,根模块是整个应用的总指挥,不应该插手过多细节。

用forRoot配置核心服务

  为应用添加服务提供商的模块也可以同时提供配置那些提供商的功能。

用CoreModule.forRoot配置核心服务

  按照约定,模块的静态方法forRoot可以同时提供并配置服务。 它接收一个服务配置对象,并返回一个ModuleWithProviders。
  根模块AppModule会导入CoreModule类并把它的providers添加到AppModule的服务提供商中。

  更精确的说法是,Angular 会先累加所有导入的提供商,然后才把它们追加到@NgModule.providers中。 这样可以确保我们显式添加到AppModule中的那些提供商总是优先于从其它模块中导入的提供商。

  现在添加CoreModule.forRoot方法,以便配置核心中的UserService。
我们曾经用一个可选的、被注入的UserServiceConfig服务扩展过核心的UserService服务。 如果有UserServiceConfig,UserService就会据此设置用户名。
app/core/user.service.ts(constructor)

constructor(@Optional() config: UserServiceConfig) { if (config) { this._userName = config.userName; }
}

这里的CoreModule.forRoot接收UserServiceConfig对象:
app/core/core.module.ts (forRoot)

static forRoot(config: UserServiceConfig): ModuleWithProviders { return { ngModule: CoreModule,providers: [ {provide: UserServiceConfig,useValue: config } ] };
}

最后,我们在AppModule的imports列表中调用它。
app/app.module.ts (imports)

imports: [
    browserModule,ContactModule,CoreModule.forRoot({userName: 'Miss Marple'}),AppRoutingModule
  ],

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

禁止重复导入CoreModule

  只有根模块AppModule才能导入CoreModule。 如果惰性加载模块导入了它,就会出问题。
  我们可以祈祷任何开发人员都不会犯错。 但是最好还是对它进行一些保护,以便让它“尽快出错”。只要把下列代码添加到CoreModule的构造函数中就可以了。

constructor (@Optional() @SkipSelf() parentModule: CoreModule) { if (parentModule) { throw new Error( 'CoreModule is already loaded. Import it in the AppModule only'); }
}

参考

  参考最终版本的全部源码:https://angular.cn/docs/ts/latest/guide/ngmodule.html。

Angular2文档学习的知识点摘要——Angular模块NgModule的更多相关文章

  1. HTML5 播放 RTSP 视频的实例代码

    目前大多数网络摄像头都是通过 RTSP 协议传输视频流的,但是 HTML 并不标准支持 RTSP 流。本文重点给大家介绍HTML5 播放 RTSP 视频的实例代码,需要的朋友参考下吧

  2. 利用Node实现HTML5离线存储的方法

    这篇文章主要介绍了利用Node实现HTML5离线存储的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. 详解如何通过H5(浏览器/WebView/其他)唤起本地app

    这篇文章主要介绍了详解如何通过H5(浏览器/WebView/其他)唤起本地app的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  4. H5混合开发app如何升级的方法

    本篇文章主要介绍了H5混合开发app如何升级的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. AmazeUI 折叠面板的实现代码

    这篇文章主要介绍了AmazeUI 折叠面板的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. HTML5手指下滑弹出负一屏阻止移动端浏览器内置下拉刷新功能的实现代码

    这篇文章主要介绍了HTML5手指下滑弹出负一屏阻止移动端浏览器内置下拉刷新功能的实现代码,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  7. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  8. html5唤起app的方法

    这篇文章主要介绍了html5唤起app的方法的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  9. HTML5拍照和摄像机功能实战详解

    这篇文章主要介绍了HTML5拍照和摄像机功能实战详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  10. ios – 在没有iPhone6s或更新的情况下测试ARKit

    我在决定下载Xcode9之前.我想玩新的框架–ARKit.我知道要用ARKit运行app我需要一个带有A9芯片或更新版本的设备.不幸的是我有一个较旧的.我的问题是已经下载了新Xcode的人.在我的情况下有可能运行ARKit应用程序吗?那个或其他任何模拟器?任何想法或我将不得不购买新设备?解决方法任何iOS11设备都可以使用ARKit,但是具有高质量AR体验的全球跟踪功能需要使用A9或更高版本处理器的设备.使用iOS11测试版更新您的设备是必要的.

随机推荐

  1. Angular2 innerHtml删除样式

    我正在使用innerHtml并在我的cms中设置html,响应似乎没问题,如果我这样打印:{{poi.content}}它给了我正确的内容:``但是当我使用[innerHtml]=“poi.content”时,它会给我这个html:当我使用[innerHtml]时,有谁知道为什么它会剥离我的样式Angular2清理动态添加的HTML,样式,……

  2. 为Angular根组件/模块指定@Input()参数

    我有3个根组件,由根AppModule引导.你如何为其中一个组件指定@input()参数?也不由AppModalComponent获取:它是未定义的.据我所知,你不能将@input()传递给bootstraped组件.但您可以使用其他方法来做到这一点–将值作为属性传递.index.html:app.component.ts:

  3. angular-ui-bootstrap – 如何为angular ui-bootstrap tabs指令指定href参数

    我正在使用角度ui-bootstrap库,但我不知道如何为每个选项卡指定自定义href.在角度ui-bootstrap文档中,指定了一个可选参数select(),但我不知道如何使用它来自定义每个选项卡的链接另一种重新定义问题的方法是如何使用带有角度ui-bootstrap选项卡的路由我希望现在还不算太晚,但我今天遇到了同样的问题.你可以通过以下方式实现:1)在控制器中定义选项卡href:2)声明一个函数来改变控制器中的散列:3)使用以下标记:我不确定这是否是最好的方法,我很乐意听取别人的意见.

  4. 离子框架 – 标签内部的ng-click不起作用

    >为什么标签标签内的按钮不起作用?>但是标签外的按钮(登陆)工作正常,为什么?>请帮我解决这个问题.我需要在点击时做出回复按钮workingdemo解决方案就是不要为物品使用标签.而只是使用divHTML

  5. Angular 2:将值传递给路由数据解析

    我正在尝试编写一个DataResolver服务,允许Angular2路由器在初始化组件之前预加载数据.解析器需要调用不同的API端点来获取适合于正在加载的路由的数据.我正在构建一个通用解析器,而不是为我的许多组件中的每个组件设置一个解析器.因此,我想在路由定义中传递指向正确端点的自定义输入.例如,考虑以下路线:app.routes.ts在第一个实例中,解析器需要调用/path/to/resourc

  6. angularjs – 解释ngModel管道,解析器,格式化程序,viewChangeListeners和$watchers的顺序

    换句话说:如果在模型更新之前触发了“ng-change”,我可以理解,但是我很难理解在更新模型之后以及在完成填充更改之前触发函数绑定属性.如果您读到这里:祝贺并感谢您的耐心等待!

  7. 角度5模板形式检测形式有效性状态的变化

    为了拥有一个可以监听其包含的表单的有效性状态的变化的组件并执行某些组件的方法,是reactiveforms的方法吗?

  8. Angular 2 CSV文件下载

    我在springboot应用程序中有我的后端,从那里我返回一个.csv文件WheniamhittingtheURLinbrowsercsvfileisgettingdownloaded.现在我试图从我的角度2应用程序中点击此URL,代码是这样的:零件:服务:我正在下载文件,但它像ActuallyitshouldbeBook.csv请指导我缺少的东西.有一种解决方法,但您需要创建一个页面上的元

  9. angularjs – Angular UI-Grid:过滤后如何获取总项数

    提前致谢:)你应该避免使用jQuery并与API进行交互.首先需要在网格创建事件中保存对API的引用.您应该已经知道总行数.您可以使用以下命令获取可见/已过滤行数:要么您可以使用以下命令获取所选行的数量:

  10. angularjs – 迁移gulp进程以包含typescript

    或者我应该使用tsc作为我的主要构建工具,让它解决依赖关系,创建映射文件并制作捆绑包?

返回
顶部