非常教程

AngularJS参考手册

Angular 模块

单例应用

前提条件:

  • 对引导有基本的了解。
  • 熟悉服务提供商。

本页中描述的这种全应用级单例服务的例子位于在线例子 / 下载范例,它示范了 NgModule 的所有已文档化的特性。


提供单例服务

在 Angular 中有两种方式来生成单例服务:

  • 声明该服务应该在应用的根上提供。
  • 把该服务包含在 AppModule 或某个只会被 AppModule 导入的模块中。

从 Angular 6.0 开始,创建单例服务的首选方式是在那个服务类上指定它应该在应用的根上提供。只要在该服务的 @Injectable 装饰器上把 providedIn 设置为 root 就可以了:

src/app/user.service.0.ts

content_copyimport { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class UserService {
}

要想深入了解关于服务的信息,参见《英雄指南》教程中的服务一章。

forRoot()

如果某个模块同时提供了服务提供商和可声明对象(组件、指令、管道),那么当在某个子注入器中加载它的时候(比如路由),就会生成多个该服务提供商的实例。 而存在多个实例会导致一些问题,因为这些实例会屏蔽掉根注入器中该服务提供商的实例,而它的本意可能是作为单例对象使用的。 因此,Angular 提供了一种方式来把服务提供商从该模块中分离出来,以便该模块既可以带着 providers 被根模块导入,也可以不带 providers 被子模块导入。

  1. 在该模块上创建一个静态方法 forRoot()(习惯名称)。
  2. 把那些服务提供商放进 forRoot 方法中,参见下面的例子。

RouterModule 为例具体说说。RouterModule 要提供 Router 服务,还要提供 RouterOutlet 指令。 RouterModule 要由根应用模块导入,以便该应用拥有一个路由器,而且它还需要至少一个 RouterOutletRouterModule 还必须由各个独立的路由组件导入,让它们能在自己的模板中使用 RouterOutlet 指令来支持其子路由。

如果 RouterModule 没有 forRoot(),那么每个路由组件都会创建一个新的 Router 实例。这将会破坏整个应用,因为应用中只能有一个 RouterRouterModule 拥有 RouterOutlet 指令,它应该随处可用,但是 Router只能有一个,它应该在 forRoot() 中提供。 最终的结果就是,应用的根模块导入了 RouterModule.forRoot(...) 以获取 Router,而所有路由组件都导入了 RouterModule,它不包括这个 Router 服务。

如果你有一个同时提供服务提供商和可声明对象的模块,请使用下面的模式把它们分离开。

那些需要把服务提供商加到应用中的模块可以通过某种类似 forRoot() 方法的方式配置那些服务提供商。

forRoot() 接收一个服务配置对象,然后返回一个 ModuleWithProviders,它是一个带有下列属性的简单对象:

  • ngModule: 在这个例子中就是 CoreModule
  • providers - 配置好的服务提供商

在这个在线例子 / 下载范例中,根 AppModule 导入了 CoreModule,并把它的 providers 添加到了 AppModule 的服务提供商中。 特别是,Angular 会在 @NgModule.providers 前面添加这些导入的服务提供商。 这种顺序保证了 AppModule 中的服务提供商总是会优先于那些从其它模块中导入的服务提供商。

应该只在 AppModule 中导入 CoreModule 并只使用一次 forRoot() 方法,因为该方法中会注册服务,而你希望那些服务在该应用中只注册一次。 如果你多次注册它们,就可能会得到该服务的多个实例,并导致运行时错误。

你还可以在 CoreModule 中添加一个用于配置 UserServiceforRoot()方法。

在下面的例子中,可选的注入 UserServiceConfig 扩展了 Core 模块中的 UserService。如果 UserServiceConfig 存在,就从这个配置中设置用户名。

src/app/core/user.service.ts (constructor)

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

下面是一个接受 UserServiceConfig 参数的 forRoot() 方法:

src/app/core/core.module.ts (forRoot)

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

最后,在 AppModuleimports列表中调用它。

src/app/app.module.ts (imports)

content_copyimport { CoreModule } from './core/core.module';
/* . . . */
@NgModule({
  imports: [
    BrowserModule,
    ContactModule,
    CoreModule.forRoot({userName: 'Miss Marple'}),
    AppRoutingModule
  ],
/* . . . */
})
export class AppModule { }

该应用不再显示默认的 “Sherlock Holmes”,而是用 “Miss Marple” 作为用户名称。

记住,在文件顶部使用 JavaScript 的 import 语句导入 CoreModule,但不要在多于一个 @NgModuleimports 列表中添加它。

防止重复导入 CoreModule

只有根模块 AppModule 才能导入 CoreModule。如果一个惰性加载模块也导入了它, 该应用就会为服务生成多个实例。

要想防止惰性加载模块重复导入 CoreModule,可以添加如下的 CoreModule 构造函数。

src/app/core/core.module.ts

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

这个构造函数要求 Angular 把 CoreModule 注入到它自己。 如果 Angular 在当前注入器中查找 CoreModule,这个注入过程就会陷入死循环。 而 @SkipSelf 装饰器表示 “在注入器树中那些高于我的祖先注入器中查找 CoreModule”。

如果构造函数在 AppModule 中执行,那就没有祖先注入器能提供 CoreModule 的实例,于是注入器就会放弃查找。

默认情况下,当注入器找不到想找的提供商时,会抛出一个错误。 但 @Optional 装饰器表示找不到该服务也无所谓。 于是注入器会返回 nullparentModule 参数也就被赋成了空值,而构造函数没有任何异常。

但如果你把 CoreModule 导入到像 CustomerModule 这样的惰性加载模块中,事情就不一样了。

Angular 会创建一个惰性加载模块,它具有自己的注入器,它是根注入器的子注入器@SkipSelf 让 Angular 在其父注入器中查找 CoreModule,这次,它的父注入器却是根注入器了(而上次的父注入器是空)。 当然,这次它找到了由根模块 AppModule 导入的实例。 该构造函数检测到存在 parentModule,于是抛出一个错误。

以下这两个文件仅供参考:

app.module.ts

core.module.ts

content_copyimport { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

/* App Root */
import { AppComponent } from './app.component';

/* Feature Modules */
import { ContactModule } from './contact/contact.module';
import { CoreModule } from './core/core.module';

/* Routing Module */
import { AppRoutingModule } from './app-routing.module';


@NgModule({
  imports: [
    BrowserModule,
    ContactModule,
    CoreModule.forRoot({userName: 'Miss Marple'}),
    AppRoutingModule
  ],
  providers: [],
  declarations: [
    AppComponent
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

关于 NgModule 的更多知识link

你还可能对下列内容感兴趣:

  • 共享模块解释了本页中涉及的这些概念。
  • 惰性加载模块。
  • NgModule 常见问题。
AngularJS

Angular 是一个开发平台。它能帮你更轻松的构建 Web 应用。Angular 集声明式模板、依赖注入、端到端工具和一些最佳实践于一身,为你解决开发方面的各种挑战。

AngularJS目录

1.快速上手 | quick start
2.语言服务
3.安全
4.环境准备与部署
5.Service Worker
6.保持最新
7.从 AngularJS 升级
8.服务端渲染
9.Visual Studio 2015 快速上手
10.风格指南
11.国际化
12.测试
13.路由与导航
14. 教程 | Tutorial
15.架构
16.组件与模板
17.表单
18.可观察对象与RxJS
19.引导启动
20.Angular 模块
21.依赖注入
22.HttpClient
23.词汇表
24.AngularJS 应用
25.AngularJS 模块
26.AngularJS 事件
27.AngularJS HTML DOM
28.AngularJS 过滤器
29.AngularJS 控制器
30.AngularJS 指令
31.AngularJS 表达式
32.AngularJS 简介
33.AngularJS 参考手册
34.AngularJS 实例
35.AngularJS 输入验证
36.AngularJS 表单
37.AngularJS SQL
38.AngularJS 表格
39.AngularJS Http
40.AngularJS 包含
41.AngularJS Bootstrap
42.AngularJS API
43.AngularJS ng-checked 指令
44.AngularJS ng-change 指令
45.AngularJS ng-blur 指令
46.AngularJS ng-bind-template 指令
47.AngularJS ng-bind-html 指令
48.AngularJS ng-bind 指令
49.AngularJS ng-app 指令
50.AngularJS Scope(作用域)
51.AngularJS ng-model 指令
52.AngularJS ng-dblclick 指令
53.AngularJS ng-cut 指令
54.AngularJS ng-csp 指令
55.AngularJS ng-copy 指令
56.AngularJS ng-controller 指令
57.AngularJS ng-cloak 指令
58.AngularJS ng-click 指令
59.AngularJS ng-class-odd 指令
60.AngularJS ng-class-even 指令
61.AngularJS ng-class 指令
62.AngularJS ng-keyup 指令
63.AngularJS ng-keypress 指令
64.AngularJS ng-keydown 指令
65.AngularJS ng-init 指令
66.AngularJS ng-include 指令
67.AngularJS ng-if 指令
68.AngularJS ng-href 指令
69.AngularJS ng-hide 指令
70.AngularJS ng-focus 指令
71.AngularJS ng-disabled 指令
72.AngularJS ng-non-bindable 指令
73.AngularJS ng-mouseup 指令
74.AngularJS ng-mouseover 指令
75.AngularJS ng-mousemove 指令
76.AngularJS ng-mouseleave 指令
77.AngularJS ng-mouseenter 指令
78.AngularJS ng-mousedown 指令
79.AngularJS ng-model-options 指令
80.AngularJS ng-model 指令
81.AngularJS ng-list 指令
82.AngularJS ng-style 指令
83.AngularJS ng-srcset 指令
84.AngularJS ng-src 指令
85.AngularJS ng-show 指令
86.AngularJS ng-selected 指令
87.AngularJS ng-repeat 指令
88.AngularJS ng-readonly 指令
89.AngularJS ng-paste 指令
90.AngularJS ng-options 指令
91.AngularJS ng-open 指令
92.AngularJS ng-value 指令
93.AngularJS ng-switch 指令
94.AngularJS ng-submit 指令
95.AngularJS 服务(Service)
96.AngularJS Select(选择框)
97.AngularJS 动画
98.AngularJS 依赖注入