Angular模块 (NgModule)

Angular Modules help organize an application into cohesive blocks of functionality.

Angular 模块能帮你把应用组织成多个内聚的功能块。

An Angular Module is a class adorned with the @NgModule decorator function. @NgModule takes a metadata object that tells Angular how to compile and run module code. It identifies the module's own components, directives and pipes, making some of them public so external components can use them. It may add service providers to the application dependency injectors. And there are many more options covered here.

Angular 模块是带有 @NgModule 装饰器函数的@NgModule接收一个元数据对象,该对象告诉 Angular 如何编译和运行模块代码。 它标记出该模块拥有的组件、指令和管道, 并把它们的一部分公开出去,以便外部组件使用它们。 它可以向应用的依赖注入器中添加服务提供商。 本章还会涉及到更多选项。

The Root Module page introduced Angular Modules and the essentials of creating and maintaining a single root AppModule for the entireapplication . Read that first.

根模块章介绍过 Angular 模块,以及如何为整个应用创建和维护单一的AppModule类。 先阅读这一章。

This page goes into much greater depth as this extensive table of contents reveals.


Table of Contents



This page explains Angular Modules through a progression of improvements to a sample with a "Tour of Heroes" theme. Here's an index to live examples at key moments in the evolution of that sample:

本章通过一个基于《英雄指南》的渐进式例子解释了 Angular 的模块。这里是例子演化过程中一些关键节点的在线例子。

Frequently Asked Questions (FAQs)


This page covers Angular Module concepts in a tutorial fashion.

本章涵盖了英雄指南下的 Angular 模块概念。

The companion Angular Module FAQs cookbook offers ready answers to specific design and implementation questions. Read this page first before hopping over to those FAQs.

烹饪宝典中的 Angular 模块常见问题为一些与设计和实现有关的问题提供了答案。 不过在阅读常见问题之前,要先阅读本章。

Angular Modularity

Angular 模块化

Modules are a great way to organize the application and extend it with capabilities from external libraries.


Many Angular libraries are modules (e.g, FormsModule, HttpModule, RouterModule). Many third party libraries are available as Angular modules (e.g., Material Design, Ionic, AngularFire2).

很多 Angular 库都是模块,例如:FormsModuleHttpModuleRouterModule。 很多第三方库也封装成了 Angular 模块,例如:Material DesignIonicAngularFire2

Angular modules consolidate components, directives and pipes into cohesive blocks of functionality, each focused on a feature area, application business domain, workflow, or common collection of utilities.

Angular 模块把组件、指令和管道打包成内聚的功能块,每个模块聚焦于一个特性区域、业务领域、工作流或通用工具。

Modules can also add services to the application. Such services might be internally-developed such as the application logger. They can come from outside sources such as the Angular router and Http client.

模块还能用来把服务加到应用程序中。这些服务可能是内部研发的,例如应用日志服务; 也可能是外部资源,例如 Angular 路由和 Http 客户端。

Modules can be loaded eagerly when the application starts. They can also be lazy loaded asynchronously by the router.


An Angular module is a class decorated with @NgModule metadata. The metadata:

Angular 模块是一个由@NgModule装饰器提供元数据的类,元数据包括:

Every Angular app has at least one module class, the root module. We bootstrap that module to launch the application.

每个 Angular 应用至少有一个模块类 —— 根模块,我们将通过引导根模块来启动应用。

The root module is all we need in a simple application with a few components. As the app grows, we refactor the root module into feature modules that represent collections of related functionality. We then import these modules into the root module.

对于组件很少的简单应用来说,只用一个根模块就足够了。 随着应用规模的增长,我们逐步从根模块中重构出一些特性模块,来代表一组相关功能的集合。 然后,我们在根模块中导入它们。

We'll see how later in the page. Let's start with the root module.


AppModule - the application root module

AppModule - 应用的根模块

Every Angular app has a root module class. By convention it's a class called AppModule in a file named app.module.ts.

每个 Angular 应用都有一个根模块类。 按照约定,它的类名叫做AppModule,被放在app.module.ts文件中。

The AppModule from the QuickStart seed is about as minimal as it gets:


app/app.module.ts (minimal)

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

The @NgModule decorator defines the metadata for the module. We'll take an intuitive approach to understanding the metadata and fill in details as we go.

@NgModule装饰器用来为模块定义元数据。 我们先凭直觉来理解一下元数据,接下来再逐步深入细节。

This metadata imports a single helper module, BrowserModule, the module every browser app must import.


BrowserModule registers critical application service providers. It also includes common directives like NgIf and NgFor which become immediately visible and usable in any of this modules component templates.

BrowserModule注册了一些关键的应用服务提供商。 它还包括了一些通用的指令,例如NgIfNgFor,所以这些指令在该模块的任何组件模板中都是可用的。

The declarations list identifies the application's only component, the root component, the top of this app's rather bare component tree.


The example AppComponent simply displays a data-bound title:


app/app.component.ts (minimal)

import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: '<h1>{{title}}</h1>', }) export class AppComponent { title = 'Minimal NgModule'; }

Lastly, the @NgModule.bootstrap property identifies this AppComponent as the bootstrap component. When Angular launches the app, it places the HTML rendering of AppComponent in the DOM, inside the <my-app> element tags of the index.html

最后,@NgModule.bootstrap属性把这个AppComponent标记为引导 (bootstrap) 组件。 当 Angular 引导应用时,它会在 DOM 中渲染AppComponent,并把结果放进index.html<my-app>元素标记内部。

Bootstrapping in main.ts

main.ts 中引导

We launch the application by bootstrapping the AppModule in the main.ts file.


Angular offers a variety of bootstrapping options, targeting multiple platforms. In this page we consider two options, both targeting the browser.

针对不同的平台,Angular 提供了很多引导选项。 本章我们只讲两个选项,都是针对浏览器平台的。

Dynamic bootstrapping with the Just-in-time (JiT) compiler

通过即时 (JiT) 编译器动态引导

In the first, dynamic option, the Angular compiler compiles the application in the browser and then launches the app.

先看看动态选项,Angular 编译器在浏览器中编译并引导该应用。

app/main.ts (dynamic)

// The browser platform with a compiler import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; // The app module import { AppModule } from './app.module'; // Compile and launch the module platformBrowserDynamic().bootstrapModule(AppModule);

The samples in this page demonstrate the dynamic bootstrapping approach.


Try the live example.

Static bootstrapping with the Ahead-of-time (AoT) compiler

使用预编译器 (AoT - Ahead-Of-Time) 进行静态引导

Consider the static alternative which can produce a much smaller application that launches faster, especially on mobile devices and high latency networks.


In the static option, the Angular compiler runs ahead-of-time as part of the build process, producing a collection of class factories in their own files. Among them is the AppModuleNgFactory.

使用静态选项,Angular 编译器作为构建流程的一部分提前运行,生成一组类工厂。它们的核心就是AppModuleNgFactory

The syntax for bootstrapping the pre-compiled AppModuleNgFactory is similar to the dynamic version that bootstraps the AppModule class.


app/main.ts (static)

// The browser platform without a compiler import { platformBrowser } from '@angular/platform-browser'; // The app module factory produced by the static offline compiler import { AppModuleNgFactory } from './app.module.ngfactory'; // Launch with the app module factory. platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);

Because the entire application was pre-compiled, we don't ship the Angular Compiler to the browser and we don't compile in the browser.

由于整个应用都是预编译的,所以我们不用把 Angular 编译器一起发到浏览器中,也不用在浏览器中进行编译。

The application code downloaded to the browser is much smaller than the dynamic equivalent and it is ready to execute immediately. The performance boost can be significant.


Both the JiT and AoT compilers generate an AppModuleNgFactory class from the same AppModule source code. The JiT compiler creates that factory class on the fly, in memory, in the browser. The AoT compiler outputs the factory to a physical file that we're importing here in the static version of main.ts.

无论是 JiT 还是 AoT 编译器都会从同一份AppModule源码中生成一个AppModuleNgFactory类。 JiT 编译器动态地在浏览器的内存中创建这个工厂类。 AoT 编译器把工厂输出成一个物理文件,也就是我们在静态版本main.ts中导入的那个。

In general, the AppModule should neither know nor care how it is bootstrapped.


Although the AppModule evolves as the app grows, the bootstrap code in main.ts doesn't change. This is the last time we'll look at main.ts.

虽然AppModule会随着应用而演化,但是main.ts中的引导代码不会变。 这将是我们最后一次关注main.ts了。

Declare directives and components


The app evolves. The first addition is a HighlightDirective, an attribute directive that sets the background color of the attached element.

应用继续演进。 首先加入的是HighlightDirective,一个属性型指令,它会设置所在元素的背景色。


import { Directive, ElementRef, Renderer } from '@angular/core'; @Directive({ selector: '[highlight]' }) /** Highlight the attached element in gold */ export class HighlightDirective { constructor(renderer: Renderer, el: ElementRef) { renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'gold'); console.log( `* AppRoot highlight called for ${el.nativeElement.tagName}`); } }

We update the AppComponent template to attach the directive to the title:


template: '<h1 highlight>{{title}}</h1>'

If we ran the app now, Angular would not recognize the highlight attribute and would ignore it. We must declare the directive in AppModule.

如果我们现在就运行该应用,Angular 将无法识别highlight属性,并且忽略它。 我们必须在AppModule中声明该指令。

Import the HighlightDirective class and add it to the module's declarations like this:


declarations: [ AppComponent, HighlightDirective, ],

Add a component


We decide to refactor the title into its own TitleComponent. The component's template binds to the component's title and subtitle properties like this:

我们决定将标题重构到独立的TitleComponent中。 该组件的模板绑定到了组件的titlesubtitle属性中,就像这样:


<h1 highlight>{{title}} {{subtitle}}</h1>


import { Component, Input } from '@angular/core'; @Component({ moduleId:, selector: 'app-title', templateUrl: 'title.component.html', }) export class TitleComponent { @Input() subtitle = ''; title = 'Angular Modules'; }

We rewrite the AppComponent to display the new TitleComponent in the <app-title> element, using an input binding to set the subtitle.


app/app.component.ts (v1)

import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: '<app-title [subtitle]="subtitle"></app-title>' }) export class AppComponent { subtitle = '(v1)'; }

Angular won't recognize the <app-title> tag until we declare it in AppModule. Import the TitleComponent class and add it to the module's declarations:

除非我们在AppModule中声明过,否则 Angular 无法识别<app-title>标签。 导入TitleComponent类,并把它加到模块的declarations中:

declarations: [ AppComponent, HighlightDirective, TitleComponent, ],

Service Providers


Modules are a great way to provide services for all of the module's components.


The Dependency Injection page describes the Angular hierarchical dependency injection system and how to configure that system with providers at different levels of the application's component tree.

依赖注入一章中讲过 Angular 的层次化依赖注入系统, 以及如何在组件树的不同层次上通过提供商配置该系统。

A module can add providers to the application's root dependency injector, making those services available everywhere in the application.


Many applications capture information about the currently logged-in user and make that information accessible through a user service. This sample application has a dummy implementation of such a UserService.

很多应用都需要获取当前登录的用户的信息,并且通过一个用户服务来访问它们。 该范例中有一个UserService的伪实现。


import { Injectable } from '@angular/core'; @Injectable() /** Dummy version of an authenticated user service */ export class UserService { userName = 'Sherlock Holmes'; }

The sample application should display a welcome message to the logged in user just below the application title. Update the TitleComponent template to show the welcome message below the application title.

该范例应用会在标题下方为已登录用户显示一条欢迎信息。 更新TitleComponent的模板来显示它。


<h1 highlight>{{title}} {{subtitle}}</h1> <p *ngIf="user"> <i>Welcome, {{user}}</i> <p>

Update the TitleComponent class with a constructor that injects the UserService and sets the component's user property from the service.



import { Component, Input } from '@angular/core'; import { UserService } from './user.service'; @Component({ moduleId:, selector: 'app-title', templateUrl: 'title.component.html', }) export class TitleComponent { @Input() subtitle = ''; title = 'Angular Modules'; user = ''; constructor(userService: UserService) { this.user = userService.userName; } }

We've defined and used the service. Now we provide it for all components to use by adding it to a providers property in the AppModule metadata:


app/app.module.ts (providers)

providers: [ UserService ],

Import supporting modules


The app shouldn't welcome a user if there is no user.


Notice in the revised TitleComponent that an *ngIf directive guards the message. There is no message if there is no user.


app/title.component.html (ngIf)

<p *ngIf="user"> <i>Welcome, {{user}}</i> <p>

Although AppModule doesn't declare NgIf, the application still compiles and runs. How can that be? The Angular compiler should either ignore or complain about unrecognized HTML.

虽然AppModule没有声明过NgIf指令,但该应用仍然能正常编译和运行。为什么这样没问题呢?Angular 的编译器遇到不认识的 HTML 时应该不是忽略就是报错才对。

Angular does recognize NgIf because we imported it earlier. The initial version of AppModule imports BrowserModule.

Angular 能识别NgIf指令,是因为我们以前导入过它。最初版本的AppModule就导入了BrowserModule

app/app.module.ts (imports)

imports: [ BrowserModule ],

Importing BrowserModule made all of its public components, directives and pipes visible to the component templates in AppModule. They are ready to use without further ado.


More accurately, NgIf is declared in CommonModule from @angular/common.


CommonModule contributes many of the common directives that applications need including ngIf and ngFor.


BrowserModule imports CommonModule and re-exports it. The net effect is that an importer of BrowserModule gets CommonModule directives automatically.

BrowserModule导入了CommonModule并且重新导出了它。 最终的效果是:只要导入BrowserModule就自动获得了CommonModule中的指令。

Many familiar Angular directives do not belong toCommonModule. For example, NgModel and RouterLink belong to Angular's FormsModule and RouterModule respectively. We must import those modules before we can use their directives.

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

To illustrate this point, we extend the sample app with ContactComponent, a form component that imports form support from the Angular FormsModule.

要解释这一点,我们可以再加入ContactComponent组件,它是一个表单组件,从 Angular 的FormsModule中导入了表单支持。

Add the ContactComponent

添加 ContactComponent

Angular Forms are a great way to manage user data entry.

Angular 表单是用来管理用户数据输入的最佳方式之一。

The ContactComponent presents a "contact editor", implemented with Angular Forms in the template-driven form style.


Angular Form Styles

Angular 表单的风格

We write Angular form components in either the template-driven form style or the reactive form style.

我们写 Angular 表单组件时,可以使用模板驱动式表单, 也可以使用响应式表单

This sample is about to import the FormsModule from @angular/forms because the ContactComponent is written in the template-driven style. Modules with components written in the reactive style, should import the ReactiveFormsModule instead.

该例子中从@angular/forms中导入了FormsModule,这是因为ContactComponent组件用的是模板驱动式表单。 那些带有响应式表单组件的模块,应该转而导入ReactiveFormsModule

The ContactComponent selector matches an element named <app-contact>. Add an element with that name to the AppComponent template just below the <app-title>:

ContactComponent的选择器会去匹配名叫<app-contact>的元素。 在AppComponent模板中<app-title>的下方添加一个具有此名字的元素:

app/app.component.ts (template)

template: ` <app-title [subtitle]="subtitle"></app-title> <app-contact></app-contact> `

The ContactComponent has a lot going on. Form components are often complex anyway and this one has its own ContactService, its own custom pipe called Awesome, and an alternative version of the HighlightDirective.

ContactComponent还有很多事要做。 表单组件通常都是很复杂的。本组件具有它自己的ContactService自定义管道 Awesome, 以及HighlightDirective的另一个版本。

To make it manageable, we place all contact-related material in an app/contact folder and break the component into three constituent HTML, TypeScript, and css files:

为了方便管理,我们把所有与联系人相关的编程元素都放进app/contact目录, 并把该组件分解成三个基本成分:HTML、TypeScript 和 CSS 文件:

<h2>Contact of {{userName}}</h2> <div *ngIf="msg" class="msg">{{msg}}</div> <form *ngIf="contacts" (ngSubmit)="onSubmit()" #contactForm="ngForm"> <h3 highlight>{{ | awesome }}</h3> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" required [(ngModel)]="" name="name" #name="ngModel" > <div [hidden]="name.valid" class="alert alert-danger"> Name is required </div> </div> <br> <button type="submit" class="btn btn-default" [disabled]="!contactForm.form.valid">Save</button> <button type="button" class="btn" (click)="next()" [disabled]="!contactForm.form.valid">Next Contact</button> <button type="button" class="btn" (click)="newContact()">New Contact</button> </form> import { Component, OnInit } from '@angular/core'; import { Contact, ContactService } from './contact.service'; import { UserService } from '../user.service'; @Component({ moduleId:, selector: 'app-contact', templateUrl: 'contact.component.html', styleUrls: [ 'contact.component.css' ] }) export class ContactComponent implements OnInit { contact: Contact; contacts: Contact[]; msg = 'Loading contacts ...'; userName = ''; constructor(private contactService: ContactService, userService: UserService) { this.userName = userService.userName; } ngOnInit() { this.contactService.getContacts().then(contacts => { this.msg = ''; this.contacts = contacts; = contacts[0]; }); } next() { let ix = 1 + this.contacts.indexOf(; if (ix >= this.contacts.length) { ix = 0; } = this.contacts[ix]; } onSubmit() { // POST-DEMO TODO: do something like save it this.displayMessage('Saved ' +; } newContact() { this.displayMessage('New contact'); = {id: 42, name: ''}; this.contacts.push(; } /** Display a message briefly, then remove it. */ displayMessage(msg: string) { this.msg = msg; setTimeout(() => this.msg = '', 1500); } } .ng-valid[required] { border-left: 5px solid #42A948; /* green */ } .ng-invalid { border-left: 5px solid #a94442; /* red */ } .alert { padding: 15px; margin: 8px 0; border: 1px solid transparent; border-radius: 4px; } .alert-danger { color: #a94442; background-color: #f2dede; border-color: #ebccd1; } .msg { color: blue; background-color: whitesmoke; border: 1px solid transparent; border-radius: 4px; margin-bottom: 20px; } import { Injectable } from '@angular/core'; export class Contact { constructor(public id: number, public name: string) { } } const CONTACTS: Contact[] = [ new Contact(21, 'Sam Spade'), new Contact(22, 'Nick Danger'), new Contact(23, 'Nancy Drew') ]; const FETCH_LATENCY = 500; @Injectable() export class ContactService { getContacts() { return new Promise<Contact[]>(resolve => { setTimeout(() => { resolve(CONTACTS); }, FETCH_LATENCY); }); } getContact(id: number | string) { return this.getContacts() .then(heroes => heroes.find(hero => === +id)); } } import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'awesome' }) /** Precede the input string with the word "Awesome " */ export class AwesomePipe implements PipeTransform { transform(phrase: string) { return phrase ? 'Awesome ' + phrase : ''; } } import { Directive, ElementRef, Renderer } from '@angular/core'; @Directive({ selector: '[highlight], input' }) /** Highlight the attached element or an InputElement in blue */ export class HighlightDirective { constructor(renderer: Renderer, el: ElementRef) { renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'powderblue'); console.log( `* Contact highlight called for ${el.nativeElement.tagName}`); } }

Focus on the component template. Notice the two-way data binding [(ngModel)] in the middle of the template. ngModel is the selector for the NgModel directive.

先来看组件模板。 注意模板中部的双向数据绑定[(ngModel)]ngModelNgModel指令的选择器。

Although NgModel is an Angular directive, the Angular Compiler won't recognize it because (a) AppModule doesn't declare it and (b) it wasn't imported via BrowserModule.

虽然NgModel是 Angular 指令,但 Angular 编译器并不会识别它, 这是因为:(a) AppModule没有声明过它,并且 (b) 它也没有通过BrowserModule被导入过。

Less obviously, even if Angular somehow recognized ngModel, this ContactComponent would not behave like an Angular form because form features such as validation are not yet available.

退一步说,即使 Angular 有办法识别ngModelContactComponent也不会表现的像 Angular 表单, 因为本组件表单的表单相关的特性(例如有效性验证)还不可用。

Import the FormsModule


Add the FormsModule to the AppModule metadata's imports list.


imports: [ BrowserModule, FormsModule ],

Now [(ngModel)] binding will work and the user input will be validated by Angular Forms, once we declare our new component, pipe and directive.

一旦我们声明了这些新组件、管道和指令,[(ngModel)]绑定就会正常工作,用户的输入也能被 Angular 表单验证了。

Do not add NgModel — or the FORMS_DIRECTIVES — to the AppModule metadata's declarations!


These directives belong to the FormsModule. Components, directives and pipes belong to one module — and one module only.

这些指令属于FormsModule。 组件、指令和管道只能属于一个模块。

Never re-declare classes that belong to another module.


Declare the contact component, directive and pipe


The application fails to compile until we declare the contact component, directive and pipe. Update the declarations in the AppModule accordingly:

如果我们没有声明该联系人模块的组件、指令和管道,该应用就会失败。 更新AppModule中的declarations元数据,就像这样:

app/app.module.ts (declarations)

declarations: [ AppComponent, HighlightDirective, TitleComponent, AwesomePipe, ContactComponent, ContactHighlightDirective ],

There are two directives with the same name, both called HighlightDirective.


We work around it by creating an alias for the second, contact version using the as JavaScript import keyword:

我们只要在 import 时使用as关键字来为第二个指令创建个别名就可以了。

import { HighlightDirective as ContactHighlightDirective } from './contact/highlight.directive';

This solves the immediate problem of referencing both directive types in the same file but leaves another problem unresoved as we discuss below.


Provide the ContactService

提供 ContactService

The ContactComponent displays contacts retrieved by the ContactService which Angular injects into its constructor.

ContactComponent显示从ContactService服务中获取的联系人信息,该服务是被 Angular 注入到组件的构造函数中的。

We have to provide that service somewhere. The ContactComponent could provide it. But then it would be scoped to this component only. We want to share this service with other contact-related components that we will surely add later.

我们必须在某个地方提供该服务。 在ContactComponent可以提供它。 但是那样一来,它的作用范围就会局限于该组件及其子组件。 而我们希望让该服务与其它和联系人有关的组件中共享,稍后我们就会添加那些组件。

In this app we chose to add ContactService to the AppModule metadata's providers list:


app/app.module.ts (providers)

providers: [ ContactService, UserService ],

Now ContactService (like UserService) can be injected into any component in the application.


Application-scoped Providers


The ContactService provider is application-scoped because Angular registers a module's providers with the application's root injector.

ContactService的提供商是全应用范围的,这是因为 Angular 使用该应用的根注入器注册模块的providers

Architecturally, the ContactService belongs to the Contact business domain. Classes in other domains don't need the ContactService and shouldn't inject it.

从架构上看,ContactService属于“联系人”这个业务领域。 其它领域中的类并不需要知道ContactService,也不会要求注入它。

We might expect Angular to offer a module-scoping mechanism to enforce this design. It doesn't. Angular module instances, unlike components, do not have their own injectors so they can't have their own provider scopes.

我们可能会期待 Angular 提供一种模块范围内的机制来保障此设计。 但它没有。与组件不同,Angular的 模块实例并没有它们自己的注入器,所以它们也没有自己的供应商范围。

This omission is intentional. Angular modules are designed primarily to extend an application, to enrich the entire app with the module's capabilities.

Angular是故意这么设计的。 Angular的模块设计,主要目的是扩展应用程序,丰富其模块化能力。

Service scoping is rarely a problem in practice. Non-contact components can't inject the ContactService by accident. To inject ContactService, you must first import its type. Only Contact components should import the ContactService type.

在实践中,服务的范围很少会成为问题。 联系人之外的组件不会意外注入ContactService服务。 要想注入ContactService,你得先导入它的类型。 而只有联系人组件才会导入ContactService类型

See the FAQ that pursues this issue and its mitigations in greater detail.

参见关于此问题的FAQ, 那里有非常详细的讲解。

Run the app


Everything is now in place to run the application with its contact editor.


The app file structure looks like this:



Try the example:


Resolve directive conflicts


We ran into trouble above when we declared the contact's HighlightDirective because we already had a HighlightDirective class at the application level.


That both directives have the same name smells of trouble.


A look at their selectors reveals that they both highlight the attached element with a different color.


import { Directive, ElementRef, Renderer } from '@angular/core'; @Directive({ selector: '[highlight]' }) /** Highlight the attached element in gold */ export class HighlightDirective { constructor(renderer: Renderer, el: ElementRef) { renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'gold'); console.log( `* AppRoot highlight called for ${el.nativeElement.tagName}`); } } import { Directive, ElementRef, Renderer } from '@angular/core'; @Directive({ selector: '[highlight], input' }) /** Highlight the attached element or an InputElement in blue */ export class HighlightDirective { constructor(renderer: Renderer, el: ElementRef) { renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'powderblue'); console.log( `* Contact highlight called for ${el.nativeElement.tagName}`); } }

Will Angular use only one of them? No. Both directives are declared in this module so both directives are active.

Angular 会只用它们中的一个吗?不会。 所有指令都声明在该模块中,所以这两个指令都会被激活

When the two directives compete to color the same element, the directive declared later wins because its DOM changes overwrite the first. In this case, the contact's HighlightDirective colors the application title text blue when it should stay gold.

当两个指令在同一个元素上争相设置颜色时,后声明的那个会胜出,因为它对 DOM 的修改覆盖了前一个。 在该例子中,联系人的HighlightDirective把应用标题的文本染成了蓝色,而我们原本期望它保持金色。

The real problem is that there are two different classes trying to do the same thing.


It's OK to import the same directive class multiple times. Angular removes duplicate classes and only registers one of them.

多次导入同一个指令是没问题的,Angular 会移除重复的类,而只注册一次。

But these are actually two different classes, defined in different files, that happen to have the same name.


They're not duplicates from Angular's perspective. Angular keeps both directives and they take turns modifying the same HTML element.

从 Angular 的角度看,两个类并没有重复。Angular 会同时保留这两个指令,并让它们依次修改同一个 HTML 元素。

At least the app still compiles. If we define two different component classes with the same selector specifying the same element tag, the compiler reports an error. It can't insert two components in the same DOM location.

至少,应用仍然编译通过了。 如果我们使用相同的选择器定义了两个不同的组件类,并指定了同一个元素标记,编译器就会报错说它无法在同一个 DOM 位置插入两个不同的组件。

What a mess!


We can eliminate component and directive conflicts by creating feature modules that insulate the declarations in one module from the declarations in another.

我们可以通过创建特性模块来消除组件与指令的冲突。 特性模块可以把来自一个模块中的声明和来自另一个的区隔开。

Feature Modules


This application isn't big yet. But it's already suffering structural problems.


The HighlightDirective in contact is re-coloring the work done by the HighlightDirective declared in AppModule. And it's coloring the application title text when it should only color the ContactComponent.

联系人模块的HighlightDirectiveAppModule中声明的HighlightDirective的基础上进行了二次上色。 并且,它染了应用标题文字的颜色,而不仅仅是ContactComponent中的。

We mitigate these problems with feature modules.


Feature Module


A feature module is a class adorned by the @NgModule decorator and its metadata, just like a root module. Feature module metadata have the same properties as the metadata for a root module.

特性模块是带有@NgModule装饰器及其元数据的类,就像根模块一样。 特性模块的元数据和根模块的元数据的属性是一样的。

The root module and the feature module share the same execution context. They share the same dependency injector which means the services in one module are available to all.

根模块和特性模块还共享着相同的执行环境。 它们共享着同一个依赖注入器,这意味着某个模块中定义的服务在所有模块中也都能用。

There are two significant technical differences:


  1. We boot the root module to launch the app; we import a feature module to extend the app.


  2. A feature module can expose or hide its implementation from other modules.


Otherwise, a feature module is distinguished primarily by its intent.


A feature module delivers a cohesive set of functionality focused on an application business domain, a user workflow, a facility (forms, http, routing), or a collection of related utilities.

特性模块用来提供了内聚的功能集合。 聚焦于应用的某个业务领域、用户工作流、某个基础设施(表单、HTTP、路由),或一组相关的工具集合。

While we can do everything within the root module, feature modules help us partition the app into areas of specific interest and purpose.


A feature module collaborates with the root module and with other modules through the services it provides and the components, directives, and pipes that it chooses to share.


In the next section, we carve the contact functionality out of the root module and into a dedicated feature module.


Make Contact a feature module


It's easy to refactor the contact material into a contact feature module.


  1. Create the ContactModule in the app/contact folder.


  2. Move the contact material from AppModule to ContactModule.


  3. Replace the imported BrowserModule with CommonModule.


  4. Import the ContactModule into the AppModule.


AppModule is the only existing class that changes. But we do add one new file.


Add the ContactModule

添加 ContactModule

Here's the new ContactModule



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

We copy from AppModule the contact-related import statements and the @NgModule properties that concern the contact and paste them in ContactModule.

AppModule中的相关联系人的 import 语句和@NgModule的相关属性复制到ContactModule

We import the FormsModule because the contact component needs it.


Modules do not inherit access to the components, directives or pipes that are declared in other modules. What AppModule imports is irrelevant to ContactModule and vice versa. Before ContactComponent can bind with [(ngModel)], its ContactModule must import FormsModule.

当前模块不会继承其它模块中对组件、指令或管道的访问权。 AppModule中的 imports 与ContatModule的 imports 互不相干。 如果ContactComponent要绑定到[(ngModel)],它所在的ContactModule必需导入FormsModule

We also replaced BrowserModule by CommonModule for reasons explained in an FAQ.


We declare the contact component, directive, and pipe in the module declarations.


We export the ContactComponent so other modules that import the ContactModule can include it in their component templates.


All other declared contact classes are private by default. The AwesomePipe and HighlightDirective are hidden from the rest of the application. The HighlightDirective can no longer color the AppComponent title text.

声明的所有其它联系人类默认都是私有的。 AwesomePipeHighlightDirective对应用的其它部分是不可见的。 所以HighlightDirective不能把AppComponent的标题文字染色。

Refactor the AppModule

重构 AppModule

Return to the AppModule and remove everything specific to the contact feature set.


Delete the contact import statements. Delete the contact declarations and contact providers. Remove the FormsModule from the imports list (AppComponent doesn't need it). Leave only the classes required at the application root level.

删除属于联系人的import语句。 删除联系人的declarationsproviders。 从imports列表中移除FormsModuleAppComponent并不需要它)。 只保留本应用的根一级需要的那些类。

Then import the ContactModule so the app can continue to display the exported ContactComponent.


Here's the refactored version of the AppModule side-by-side with the previous version.


import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; /* App Root */ import { AppComponent } from './app.component'; import { HighlightDirective } from './highlight.directive'; import { TitleComponent } from './title.component'; import { UserService } from './user.service'; /* Contact Imports */ import { ContactModule } from './contact/contact.module'; @NgModule({ imports: [ BrowserModule, ContactModule ], declarations: [ AppComponent, HighlightDirective, TitleComponent ], providers: [ UserService ], bootstrap: [ AppComponent ], }) export class AppModule { } import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; /* App Root */ import { AppComponent } from './app.component'; import { HighlightDirective } from './highlight.directive'; import { TitleComponent } from './title.component'; import { UserService } from './user.service'; /* Contact Imports */ import { ContactComponent } from './contact/contact.component'; import { ContactService } from './contact/contact.service'; import { AwesomePipe } from './contact/awesome.pipe'; import { HighlightDirective as ContactHighlightDirective } from './contact/highlight.directive'; import { FormsModule } from '@angular/forms'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent, HighlightDirective, TitleComponent, AwesomePipe, ContactComponent, ContactHighlightDirective ], providers: [ ContactService, UserService ], bootstrap: [ AppComponent ] }) export class AppModule { }



There's a lot to like in the revised AppModule


Try this ContactModule version of the sample.



Lazy loading modules with the Router

用路由器实现惰性 (lazy) 加载

The Heroic Staffing Agency sample app has evolved. It has two more modules, one for managing the heroes-on-staff and another for matching crises to the heroes. Both modules are in the early stages of development. Their specifics aren't important to the story and we won't discuss every line of code.

英雄职介所这个例子应用继续成长。 它又增加了两个模块,一个用来管理雇佣的英雄,另一个用来匹配英雄与危机。 这两个模块都还处于前期开发阶段。 它们对于整个故事来说无关紧要,这里我们就不逐行讨论了。

Examine and download the complete source for this version from the live example.

在线例子 试用并下载当前版本的完整代码。

Some facets of the current application merit discussion.


Let's start at the top with the new AppComponent template: a title, three links, and a <router-outlet>.


app/app.component.ts (v3 - Template)

template: ` <app-title [subtitle]="subtitle"></app-title> <nav> <a routerLink="contact" routerLinkActive="active">Contact</a> <a routerLink="crisis" routerLinkActive="active">Crisis Center</a> <a routerLink="heroes" routerLinkActive="active">Heroes</a> </nav> <router-outlet></router-outlet> `

The <app-contact> element is gone; we're routing to the Contact page now.


The AppModule has changed modestly:


app/app.module.ts (v3)

import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; /* App Root */ import { AppComponent } from './app.component.3'; import { HighlightDirective } from './highlight.directive'; import { TitleComponent } from './title.component'; import { UserService } from './user.service'; /* Feature Modules */ import { ContactModule } from './contact/contact.module.3'; /* Routing Module */ import { AppRoutingModule } from './app-routing.module.3'; @NgModule({ imports: [ BrowserModule, ContactModule, AppRoutingModule ], providers: [ UserService ], declarations: [ AppComponent, HighlightDirective, TitleComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }

Some file names bear a .3 extension indicating a difference with prior or future versions. We'll explain differences that matter in due course.

有些文件名带有.3扩展名,用来和以前/以后的版本区分开。 我们会在适当的时机解释它们的差异。

The module still imports ContactModule so that its routes and components are mounted when the app starts.


The module does not import HeroModule or CrisisModule. They'll be fetched and mounted asynchronously when the user navigates to one of their routes.

该模块导入HeroModuleCrisisModule。 它们将在用户导航到其中的某个路由时,被异步获取并加载。

The significant change from version 2 is the addition of the AppRoutingModule to the module imports. The AppRoutingModule is a Routing Module that handles the app's routing concerns.

与第二版相比,最值得注意的修改是imports中那个额外的AppRoutingModule模块。 AppRoutingModule是一个路由模块 用来处理应用的路由。

App routing



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 {}

The router is the subject of its own page so we'll skip lightly over the details and concentrate on the intersection of Angular modules and routing.

路由器有专门的章节做深入讲解,所以这里我们跳过细节,而是专注于它和 Angular 模块的协作。

This file defines three routes.


The first redirects the empty URL (e.g., to another route whose path is contact (e.g.,

第一个路由把空白 URL(例如重定向到了另一个路径为contact的路由(例如。

The contact route isn't defined here. It's defined in the Contact feature's own routing module, contact-routing.module.ts. It's standard practice for feature modules with routing components to define their own routes. We'll get to that file in a moment.

contact路由并不是在这里定义的,而是定义在联系人特性区自己的路由文件contact.routing.ts中。 对于带有路由组件的特性模块,其标准做法就是让它们定义自己的路由。 稍后我们就会看到这些。

The remaining two routes use lazy loading syntax to tell the router where to find the modules:


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

A lazy loaded module location is a string, not a type. In this app, the string identifies both the module file and the module class, the latter separated from the former by a #.

惰性加载模块的位置是字符串而不是类型。 在本应用中,该字符串同时标记出了模块文件和模块,两者用#分隔开。


The forRoot static class method of the RouterModule with the provided configuration, added to the imports array provides the routing concerns for the module.


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

The returned AppRoutingModule class is a Routing Module containing both the RouterModule directives and the Dependency Injection providers that produce a configured Router.


This AppRoutingModule is intended for the app root module only.


Never call RouterModule.forRoot in a feature routing module.


Back in the root AppModule, we add the AppRoutingModule to its imports list, and the app is ready to navigate.


app/app.module.ts (imports)

imports: [ BrowserModule, ContactModule, AppRoutingModule ],

Routing to a feature module


The app/contact folder holds a new file, contact-routing.module.ts. It defines the contact route we mentioned a bit earlier and also provides a ContactRoutingModule like so:

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

app/contact/contact-routing.module.ts (routing)

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

This time we pass the route list to the forChild method of the RouterModule. It's only responsible for providing additional routes and is intended for feature modules.

这次我们要把路由列表传给RouterModuleforChild方法。 该方法会为特性模块生成另一种对象。

Always call RouterModule.forChild in a feature routing module.


forRoot and forChild are conventional names for methods that deliver different import values to root and feature modules. Angular doesn't recognize them but Angular developers do.

当需要为根模块和特性模块分别提供不同的导入值时,forRootforChild是约定俗成的方法名。 虽然 Angular 无法识别它们,但是 Angular 开发人员可以。

Follow this convention if you write a similar module that has both shared declarables and services.


ContactModule has changed in two small but important details


@NgModule({ imports: [ CommonModule, FormsModule, ContactRoutingModule ], declarations: [ ContactComponent, HighlightDirective, AwesomePipe ], providers: [ ContactService ] }) export class ContactModule { } @NgModule({ imports: [ CommonModule, FormsModule ], declarations: [ ContactComponent, HighlightDirective, AwesomePipe ], exports: [ ContactComponent ], providers: [ ContactService ] }) export class ContactModule { }
  1. It imports the ContactRoutingModule object from contact-routing.module.ts


  2. It no longer exports ContactComponent


Now that we navigate to ContactComponent with the router there's no reason to make it public. Nor does it need a selector. No template will ever again reference this ContactComponent. It's gone from the AppComponent template.

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

Lazy loaded routing to a module


The lazy loaded HeroModule and CrisisModule follow the same principles as any feature module. They don't look different from the eagerly loaded ContactModule.


The HeroModule is a bit more complex than the CrisisModule which makes it a more interesting and useful example. Here's its file structure:



This is the child routing scenario familiar to readers of the Router page. The HeroComponent is the feature's top component and routing host. Its template has a <router-outlet> that displays either a list of heroes (HeroList) or an editor of a selected hero (HeroDetail). Both components delegate to the HeroService to fetch and save data.

如果你读过路由那章,那么对这个子路由的场景应该觉得很熟悉。 HeroComponent是本特性区的顶级组件和路由宿主。 模板带有<router-outlet>指令,它或者显示英雄列表(HeroList)或者显示所选英雄的编辑器(HeroDetail)。 这两个组件都把获取和保存数据的任务委托给HeroService执行。

There's yet another HighlightDirective that colors elements in yet a different shade. We should do something about the repetition and inconsistencies. We endure for now.

还有另一个HighlightDirective指令,它用另一种方式为元素染色。 我们还应该做点什么来消除这种不必要的重复和不一致性。 不过目前先暂时容忍这个问题。

The HeroModule is a feature module like any other.


app/hero/hero.module.ts (class)

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

It imports the FormsModule because the HeroDetailComponent template binds with [(ngModel)]. It imports the HeroRoutingModule from hero-routing.module.ts just as ContactModule and CrisisModule do.

它导入了FormsModule,因为HeroDetailComponent的模板中绑定到了[(ngModel)]。 像ContactModuleCrisisModule中一样,它还从hero-routing.module.ts中导入了HeroRoutingModule

The CrisisModule is much the same. There's nothing more to say that's new.



Shared modules


The app is shaping up. One thing we don't like is carrying three different versions of the HighlightDirective. And there's a bunch of other stuff cluttering the app folder level that could be tucked away.

本应用在继续演进中。 让我们感到不爽的是:这里有HighlightDirective的三个不同版本。 还有一大堆其它乱七八糟的东西堆在 app 目录这一级,我们得把它们清出去。

Let's add a SharedModule to hold the common components, directives, and pipes and share them with the modules that need them.


Most of this is familiar blocking and tackling. Here is the 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, HighlightDirective, CommonModule, FormsModule ] }) export class SharedModule { }

Some highlights


Re-exporting other modules


While reviewing our application, we noticed that many components requiring SharedModule directives also use NgIf and NgFor from CommonModule and bind to component properties with [(ngModel)], a directive in the FormsModule. Modules that declare these components would have to import CommonModule, FormsModule and SharedModule.

当回顾应用程序时,我们注意到很多需要SharedModule的组件也同时用到了来自CommonModuleNgIfNgFor指令, 并且还通过来自FormsModule[(ngModel)]指令绑定到了组件的属性。 那些声明这些组件的模块将不得不同时导入CommonModuleFormsModuleSharedModule

We can reduce the repetition by having SharedModule re-export CommonModule and FormsModule so that importers of SharedModule get CommonModule and FormsModule for free.

通过让SharedModule重新导出CommonModuleFormsModule模块,我们可以消除这种重复。 于是导入SharedModule的模块也同时免费获得了CommonModuleFormsModule

As it happens, the components declared by SharedModule itself don't bind with [(ngModel)]. Technically, there is no need for SharedModule to import FormsModule.


SharedModule can still export FormsModule without listing it among its imports.


Why TitleComponent isn't shared

为什么 TitleComponent 没有被共享

SharedModule exists to make commonly used components, directives and pipes available for use in the templates of components in many other modules.


The TitleComponent is used only once by the AppComponent. There's no point in sharing it.


Why UserService isn't shared

为什么 UserService 没有被共享

While many components share the same service instances, they rely on Angular dependency injection to do this kind of sharing, not the module system.

虽然很多组件都共享着同一个服务实例,但它们是靠 Angular 的依赖注入体系实现的,而不是模块体系。

Several components of our sample inject the UserService. There should be only one instance of the UserService in the entire application and only one provider of it.

例子中的很多组件都注入了UserService。 在整个应用程序中,只应该有一个UserService的实例,并且它只应该有一个提供商。

UserService is an application-wide singleton. We don't want each module to have its own separate instance. Yet there is a real danger of that happening if the SharedModule provides the UserService.

UserService是全应用级单例。 我们不希望每个模块都各自有它的实例。 而如果由SharedModule提供UserService,就会导致铁板钉钉的危险

Do not specify app-wide singleton providers in a shared module. A lazy loaded module that imports that shared module will make its own copy of the service.

不要在共享模块中把应用级单例添加到providers中。 否则如果一个惰性加载模块导入了此共享模块,就会导致它自己也生成一份此服务的实例。

The Core module

核心 (Core) 模块

At the moment, our root folder is cluttered with the UserService and the TitleComponent that only appears in the root AppComponent. We did not include them in the SharedModule for reasons just explained.

现在,我们的根目录下只剩下UserServiceTitleComponent这两个被根组件AppComponent用到的类没有清理了。 但正如我们已经解释过的,它们无法被包含在SharedModule中。

Instead, we'll gather them in a single CoreModule that we import once when the app starts and never import anywhere else.




Again, most of this is familiar blocking and tackling. The interesting part is the 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 { }

We're importing some extra symbols from the Angular core library that we're not using yet. They'll become relevant later in this page.

我们正在从 Angular 核心库中导入一些从未用到的符号,稍后我们会接触它们。

The @NgModule metadata should be familiar. We declare the TitleComponent because this module owns it and we export it because AppComponent (which is in AppModule) displays the title in its template. TitleComponent needs the Angular NgIf directive that we import from CommonModule.

我们对@NgModule的元数据应该很熟悉。 由于该模块拥有TitleComponent,所以我们声明了它。由于AppComponent(位于AppModule模块)在模板中显示了这个标题,所以我们导出了它。 由于TitleComponent需要用到 Angular 的NgIf指令,所以我们导入了CommonModule

CoreModule provides the UserService. Angular registers that provider with the app root injector, making a singleton instance of the UserService available to any component that needs it, whether that component is eagerly or lazily loaded.

CoreModule提供UserService。Angular 在该应用的根注入器中注册了它的提供商, 导致这份UserService的实例在每个需要它的组件中都是可用的,无论那个组件时主动加载的还是惰性加载的。

Why bother?


This scenario is clearly contrived. The app is too small to worry about a single service file and a tiny, one-time component.

这个场景设计的是有点生硬。 该应用太小了,所以其实并不需要拆分出单独的服务文件和小型的、一次性的组件。

A TitleComponent sitting in the root folder isn't bothering anyone. The root AppModule can register the UserService itself, as it does currently, even if we decide to relocate the UserService file to the app/core folder.

TitleComponent放在根目录中其实也无所谓。 即使我们决定把UserService文件挪到app/core目录中,根AppModule也仍然可以自己注册UserService(就像现在这样)。

Real world apps have more to worry about. They can have several single-use components (e.g., spinners, message toasts, and modal dialogs) that appear only in the AppComponent template. We don't import them elsewhere so they're not shared in that sense. Yet they're too big and messy to leave loose in the root folder.

但真实的应用要考虑很多。 它们有一些只用于AppComponent的模板的一次性的组件(例如:加载动画、消息浮层和模态对话框等)。 我们不用在其它地方导入它们,因此没必要共享它们。 然而如果把它们留在根目录,还是显得太大、太乱了。

Apps often have many singleton services like this sample's UserService. Each must be registered exactly once, in the app root injector, when the application starts.

应用通常还有很多像这里的UserService这样的单例服务。 当程序启动时,每个服务都只能在应用的“根注入器”中注册一次

While many Components inject such services in their constructors — and therefore require JavaScript import statements to import their symbols — no other component or module should define or re-create the services themselves. Their providers are not shared.

当很多组件在它们的构造函数中注入这些服务时 — 因此也需要用 JavaScript 的import语句来导入它们的符号 — 任何组件或模块自身都不应该定义或重新创建这些服务。 因为它们的提供商不是共享的。

We recommend collecting such single-use classes and hiding their gory details inside a CoreModule. A simplified root AppModule imports CoreModule in its capacity as orchestrator of the application as a whole.

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



Having refactored to a CoreModule and a SharedModule, it's time to cleanup the other modules.


A trimmer AppModule

清理 AppModule

Here is the updated AppModule paired with version 3 for comparison:


import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; /* 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, AppRoutingModule ], declarations: [ AppComponent ], bootstrap: [ AppComponent ] }) export class AppModule { } import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; /* App Root */ import { AppComponent } from './app.component.3'; import { HighlightDirective } from './highlight.directive'; import { TitleComponent } from './title.component'; import { UserService } from './user.service'; /* Feature Modules */ import { ContactModule } from './contact/contact.module.3'; /* Routing Module */ import { AppRoutingModule } from './app-routing.module.3'; @NgModule({ imports: [ BrowserModule, ContactModule, AppRoutingModule ], providers: [ UserService ], declarations: [ AppComponent, HighlightDirective, TitleComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }

Notice that AppModule is ...


A trimmer ContactModule


Here is the new ContactModule paired with the prior version:


import { NgModule } from '@angular/core'; import { SharedModule } from '../shared/shared.module'; import { ContactComponent } from './contact.component'; import { ContactService } from './contact.service'; import { ContactRoutingModule } from './contact-routing.module'; @NgModule({ imports: [ SharedModule, ContactRoutingModule ], declarations: [ ContactComponent ], providers: [ ContactService ] }) export class ContactModule { } import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { AwesomePipe } from './awesome.pipe'; import { ContactComponent } from './contact.component.3'; import { ContactService } from './contact.service'; import { HighlightDirective } from './highlight.directive'; import { ContactRoutingModule } from './contact-routing.module.3'; @NgModule({ imports: [ CommonModule, FormsModule, ContactRoutingModule ], declarations: [ ContactComponent, HighlightDirective, AwesomePipe ], providers: [ ContactService ] }) export class ContactModule { }

Notice that


Configure core services with CoreModule.forRoot

CoreModule.forRoot 配置核心服务

A module that adds providers to the application can offer a facility for configuring those providers as well.


By convention, the forRoot static method both provides and configures services at the same time. It takes a service configuration object and returns a ModuleWithProviders which is a simple object with two properties:

按照约定,模块的静态方法forRoot可以同时提供并配置服务。 它接收一个服务配置对象,并返回一个ModuleWithProviders。这个简单对象具有两个属性:

The root AppModule imports the CoreModule and adds the providers to the AppModule providers.


More precisely, Angular accumulates all imported providers before appending the items listed in @NgModule.providers. This sequence ensures that whatever we add explicitly to the AppModule providers takes precedence over the providers of imported modules.

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

Let's add a CoreModule.forRoot method that configures the core UserService.


We've extended the core UserService with an optional, injected UserServiceConfig. If a UserServiceConfig exists, the UserService sets the user name from that config.

我们曾经用一个可选的、被注入的UserServiceConfig服务扩展过核心的UserService服务。 如果有UserServiceConfigUserService就会据此设置用户名。

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

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

Here's CoreModule.forRoot that takes a UserServiceConfig object:


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

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

Lastly, we call it within the imports list of the AppModule.


app//app.module.ts (imports)

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

The app displays "Miss Marple" as the user instead of the default "Sherlock Holmes".

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

Call forRoot only in the root application module, AppModule. Calling it in any other module, particularly in a lazy loaded module, is contrary to the intent and is likely to produce a runtime error.

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

Remember to import the result; don't add it to any other @NgModule list.


Prevent reimport of the CoreModule


Only the root AppModule should import the CoreModule. Bad things happen if a lazy loaded module imports it.

只有根模块AppModule才能导入CoreModule。 如果惰性加载模块导入了它,就会出问题

We could hope that no developer makes that mistake. Or we can guard against it and fail fast by adding the following CoreModule constructor.

我们可以祈祷任何开发人员都不会犯错。 但是最好还是对它进行一些保护,以便让它“尽快出错”。只要把下列代码添加到CoreModule的构造函数中就可以了。

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

The constructor tells Angular to inject the CoreModule into itself. That seems dangerously circular.

这个构造函数会要求 Angular 把CoreModule注入自身。这看起来像一个危险的循环注入。

The injection would be circular if Angular looked for CoreModule in the current injector. The @SkipSelf decorator means "look for CoreModule in an ancestor injector, above me in the injector hierarchy."

确实,如果 Angular 在当前注入器中查阅CoreModule,这确实会是一个循环引用。 不过,@SkipSelf装饰器意味着“在当前注入器的所有祖先注入器中寻找CoreModule。”

If the constructor executes as intended in the AppModule, there is no ancestor injector that could provide an instance of CoreModule. The injector should give up.


By default the injector throws an error when it can't find a requested provider. The @Optional decorator means not finding the service is OK. The injector returns null, the parentModule parameter is null, and the constructor concludes uneventfully.

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

It's a different story if we improperly import CoreModule into a lazy loaded module such as HeroModule (try it).


Angular creates a lazy loaded module with its own injector, a child of the root injector. @SkipSelf causes Angular to look for a CoreModule in the parent injector which this time is the root injector. Of course it finds the instance imported by the root AppModule. Now parentModule exists and the constructor throws the error.

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



You made it! You can examine and download the complete source for this final version from the live example.


Frequently Asked Questions

常见问题 (FAQ)

Now that you understand Angular Modules, you may be interested in the companion Angular Module FAQs cookbook with its ready answers to specific design and implementation questions.

现在,你已经理解了 Angular 的模块。可能你还会对烹饪宝典中的 Angular 模块常见问题感兴趣, 它解答了很多关于设计和实现方面的问题。