An NgModule 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.
@NgModule may add service providers to the application dependency injectors.
And there are many more options covered here.
Before reading this page, read the
The Root Module page, which introduces NgModules and the essentials
of creating and maintaining a single root AppModule for the entire application.
This page explains NgModules 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 the sample:
Many Angular libraries are modules (such as FormsModule, HttpModule, and RouterModule).
Many third-party libraries are available as NgModules (such as
Material Design,
Ionic,
AngularFire2).
NgModules 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.
Modules can also add services to the application.
Such services might be internally developed, such as the application logger.
Services can come from outside sources, such as the Angular router and Http client.
模块可能在应用启动时主动加载,也可能由路由器进行异步惰性加载。
Modules can be loaded eagerly when the application starts.
They can also be lazy loaded asynchronously by the router.
Angular 模块是一个由@NgModule装饰器提供元数据的类,元数据包括:
An NgModule is a class decorated with @NgModule metadata. The metadata do the following:
声明哪些组件、指令、管道属于该模块。
Declare which components, directives, and pipes belong to the module.
公开某些类,以便其它的组件模板可以使用它们。
Make some of those classes public so that other component templates can use them.
导入其它模块,从其它模块中获得本模块所需的组件、指令和管道。
Import other modules with the components, directives, and pipes needed by the components in this module.
在应用程序级提供服务,以便应用中的任何组件都能使用它。
Provide services at the application level that any application component can use.
每个 Angular 应用至少有一个模块类 —— 根模块,我们将通过引导根模块来启动应用。
Every Angular app has at least one module class, the root module.
You bootstrap that module to launch the application.
The root module is all you need in a simple application with a few components.
As the app grows, you refactor the root module into feature modules
that represent collections of related functionality.
You then import these modules into the root module.
稍后我们就会看到怎么做。不过还是先从根模块开始吧!
Later in this page, you'll read about this process. For now, you'll start with the root module.
The @NgModule decorator defines the metadata for the module.
This page takes an intuitive approach to understanding the metadata and fills in details as it progresses.
这个元数据只导入了一个辅助模块,BrowserModule,每个运行在浏览器中的应用都必须导入它。
The metadata imports a single helper module, BrowserModule, which 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 module's component templates.
declarations列出了该应用程序中唯一的组件(根组件),它是应用的光秃秃的组件树的根。
The declarations list identifies the application's only component,
the root component, the top of the app's rather bare component tree.
下面范例AppComponent显示被绑定的标题:
The example AppComponent simply displays a data-bound title:
src/app/app.component.ts (minimal)
import{Component}from'@angular/core';@Component({
selector:'my-app',template:'<h1>{{title}}</h1>',})exportclassAppComponent{
title ='Minimal NgModule';}
最后,@NgModule.bootstrap属性把这个AppComponent标记为引导 (bootstrap) 组件。
当 Angular 引导应用时,它会在 DOM 中渲染AppComponent,并把结果放进index.html的<my-app>元素标记内部。
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.
在 main.ts 中引导
Bootstrapping in main.ts
在main.ts文件中,我们通过引导AppModule来启动应用。
You launch the application by bootstrapping the AppModule in the main.ts file.
针对不同的平台,Angular 提供了很多引导选项。
本章我们只讲两个选项,都是针对浏览器平台的。
Angular offers a variety of bootstrapping options targeting multiple platforms.
This page describes two options, both targeting the browser.
通过即时 (JiT) 编译器动态引导
Dynamic bootstrapping with the just-in-time (JIT) compiler
In the first, dynamic option, the Angular compiler
compiles the application in the browser and then launches the app.
src/main.ts (dynamic)
// The browser platform with a compilerimport{ platformBrowserDynamic }from'@angular/platform-browser-dynamic';// The app moduleimport{AppModule}from'./app/app.module';// Compile and launch the module
platformBrowserDynamic().bootstrapModule(AppModule);
这里的例子演示进行动态引导的方法。
The samples in this page demonstrate the dynamic bootstrapping approach.
Static bootstrapping with the ahead-of-time (AOT) compiler
静态方案可以生成更小、启动更快的应用,建议优先使用它,特别是在移动设备或高延迟网络下。
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.
引导预编译的AppModuleNgFactory的语法和动态引导AppModule类的方式很相似。
The syntax for bootstrapping the pre-compiled AppModuleNgFactory is similar to
the dynamic version that bootstraps the AppModule class.
src/main.ts (static)
// The browser platform without a compilerimport{ platformBrowser }from'@angular/platform-browser';// The app module factory produced by the static offline compilerimport{AppModuleNgFactory}from'./app/app.module.ngfactory';// Launch with the app module factory.
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
Because the entire application was pre-compiled,
Angular doesn't ship the Angular compiler to the browser and doesn't compile in the browser.
下载到浏览器中的应用代码比动态版本要小得多,并且能立即执行。引导的性能可以得到显著提升。
The application code downloaded to the browser is much smaller than the dynamic equivalent
and it's 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 is imported here in the static version of main.ts.
通常,AppModule不必关心它是如何被引导的。
In general, the AppModule should neither know nor care how it is bootstrapped.
As the app evolves,
the first addition is a HighlightDirective, an attribute directive
that sets the background color of the attached element.
src/app/highlight.directive.ts
import{Directive,ElementRef}from'@angular/core';@Directive({ selector:'[highlight]'})/** Highlight the attached element in gold */exportclassHighlightDirective{
constructor(el:ElementRef){
el.nativeElement.style.backgroundColor ='gold';
console.log(`* AppRoot highlight called for ${el.nativeElement.tagName}`);}}
我们更新AppComponent的模板,来把该指令附加到标题上:
Update the AppComponent template to attach the directive to the title:
Angular won't recognize the <app-title> tag until you declare it in AppModule.
Import the TitleComponent class and add it to the module's declarations:
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.
模块可以往应用的“根依赖注入器”中添加提供商,让那些服务在应用中到处可用。
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.
src/app/user.service.ts
import{Injectable}from'@angular/core';@Injectable()/** Dummy version of an authenticated user service */exportclassUserService{
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.
In the revised TitleComponent, an *ngIf directive guards the message.
There is no message if there is no user.
src/app/title.component.html (ngIf)
<p *ngIf="user"><i>Welcome, {{user}}</i><p>
虽然AppModule没有声明过NgIf指令,但该应用仍然能正常编译和运行。为什么这样没问题呢?Angular 的编译器遇到不认识的 HTML 时应该不是忽略就是报错才对。
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.
Many familiar Angular directives don't belong to CommonModule.
For example, NgModel and RouterLink belong to Angular's FormsModule and RouterModule respectively.
You must import those modules before you can use their directives.
The following sample imports the FormsModule from @angular/forms because
the ContactComponent is written in template-driven style.
Modules with components written in the reactive style
import the 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>:
Form components are often complex. The ContactComponent has its own ContactService
and custom pipe (called Awesome),
and an alternative version of the HighlightDirective.
To make it manageable, place all contact-related material in an src/app/contact folder
and break the component into three constituent HTML, TypeScript, and css files:
Even if Angular somehow recognized ngModel,
ContactComponent wouldn't behave like an Angular form because
form features such as validation aren't yet available.
导入FormsModule
Import the FormsModule
把FormsModule加到AppModule元数据中的imports列表中:
Add the FormsModule to the AppModule metadata's imports list.
This solves the immediate issue of referencing both directive types in the same file but
leaves another issue unresolved.
You'll learn more about that issue later in this page, in Resolve directive conflicts.
You have to provide that service somewhere.
The ContactComponent could provide it,
but then the service would be scoped to this component only.
You want to share this service with other contact-related components that you'll surely add later.
Architecturally, the ContactService belongs to the Contact business domain.
Classes in other domains don't need the ContactService and shouldn't inject it.
You might expect Angular to offer a module-scoping mechanism to enforce this design.
It doesn't. NgModule instances, unlike components, don't have their own injectors
so they can't have their own provider scopes.
In practice, service scoping is rarely an issue.
Non-contact components can't accidentally inject the ContactService.
To inject ContactService, you must first import its type.
Only Contact components should import the ContactService type.
`* Contact highlight called for ${el.nativeElement.tagName}`);
}
}
Angular 会只用它们中的一个吗?不会。
所有指令都声明在该模块中,所以这两个指令都会被激活。
Both directives are declared in this module so both directives are active.
当两个指令在同一个元素上争相设置颜色时,后声明的那个会胜出,因为它对 DOM 的修改覆盖了前一个。
在该例子中,联系人的HighlightDirective把应用标题的文本染成了蓝色,而我们原本期望它保持金色。
When the two directives compete to color the same element,
the directive that's declared later wins because its DOM changes overwrite the first.
In this case, the contact's HighlightDirective makes the application title text blue
when it should stay gold.
真正的问题在于,有两个不同的类试图做同一件事。
The issue is that two different classes are trying to do the same thing.
多次导入同一个指令是没问题的,Angular 会移除重复的类,而只注册一次。
It's OK to import the same directive class multiple times.
Angular removes duplicate classes and only registers one of them.
从 Angular 的角度看,两个类并没有重复。Angular 会同时保留这两个指令,并让它们依次修改同一个 HTML 元素。
But from Angular's perspective, two different classes, defined in different files, that have the same name
are not duplicates. Angular keeps both directives and
they take turns modifying the same HTML element.
至少,应用仍然编译通过了。
如果我们使用相同的选择器定义了两个不同的组件类,并指定了同一个元素标记,编译器就会报错说它无法在同一个 DOM 位置插入两个不同的组件。
At least the app still compiles.
If you 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.
There are conflicting directives.
The HighlightDirective in the contact re-colors the work done by the HighlightDirective declared in AppModule.
Also, it colors the application title text when it should color only the ContactComponent.
该应用在联系人和其它特性区之间缺乏清晰的边界。
这种缺失,导致难以在不同的开发组之间分配职责。
The app lacks clear boundaries between contact functionality and other application features.
That lack of clarity makes it harder to assign development responsibilities to different teams.
我们用特性模块技术来缓解此问题。
You can resolve these issues with feature modules.
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.
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.
它们在技术上有两个显著的不同点:
The modules have the following significant technical differences:
我们引导根模块来启动应用,但导入特性模块来扩展应用。
You boot the root module to launch the app;
you import a feature module to extend the app.
特性模块可以对其它模块暴露或隐藏自己的实现。
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, user workflow, facility (forms, http, routing),
or collection of related utilities.
虽然这些都能在根模块中做,但特性模块可以帮助我们把应用切分成具有特定关注点和目标的不同区域。
While you can do everything within the root module,
feature modules help you 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 shares.
下一节,我们从根模块中把与联系人有关的功能切分到专门的特性模块中。
In the next section, you'll 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.
在src/app/contact目录下创建ContactModule。
Create the ContactModule in the src/app/contact folder.
把联系人相关的元素从AppModule移到ContactModule中。
Move the contact material from AppModule to ContactModule.
把导入BrowserModule改为导入CommonModule。
Replace the imported BrowserModule with CommonModule.
在AppModule中导入ContactModule。
Import the ContactModule into the AppModule.
AppModule是唯一有改变的已经存在的类,不过我们还会添加一个新文件。
AppModule is the only existing class that changes. But you do add one new file.
Modules don't 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.
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.
重构 AppModule
Refactor the AppModule
返回AppModule并移除专属于联系人特性下的任何东西。
Return to the AppModule and remove everything specific to the contact feature set.
删除属于联系人的import语句。
Delete the contact import statements.
删除联系人的declarations和providers。
Delete the contact declarations and contact providers.
从imports列表中移除FormsModule(AppComponent并不需要它)。
Delete the FormsModule from the imports list (AppComponent doesn't need it).
只保留本应用的根一级需要的那些类。
Leave only the classes required at the application root level.
然后,导入ContactModule,以便应用能够继续显示导出的ContactComponent。
Then import the ContactModule so the app can continue to display the exported ContactComponent.
下面是AppModule重构完的版本与之前版本的对比。
Here's the refactored version of the AppModule along with the previous version.
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 this page doesn't discuss every line of code.
Examine and download the complete source for this version from the
Some file names bear a .3 extension that indicates
a difference with prior or future versions.
The significant differences will be explained in due course.
该模块仍然要导入ContactModule模块,以便在应用启动时加载它的路由和组件。
The module still imports ContactModule so that its routes and components are mounted when the app starts.
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.
The router is the subject of the Routing & Navigation page, so this section skips many of the details and
concentrates on the intersection of NgModules and routing.
app-routing.module.ts文件定义了三个路由。
The app-routing.module.ts file defines three routes.
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.
You'll get to that file in a moment.
另外两个路由使用惰性加载语法来告诉路由器要到哪里去找这些模块。
The remaining two routes use lazy loading syntax to tell the router where to find the modules:
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 and
added to the imports array provides the routing concerns for the module.
The returned AppRoutingModule class is a Routing Module containing both the RouterModule directives
and the dependency-injection providers that produce a configured Router.
这个AppRoutingModule仅用于应用的根模块。
This AppRoutingModule is intended for the app root module only.
永远不要在特性路由模块中调用RouterModule.forRoot!
Never call RouterModule.forRoot in a feature-routing module.
The src/app/contact folder holds a new file, contact-routing.module.ts.
It defines the contact route mentioned earlier and provides a ContactRoutingModule as follows:
This time you pass the route list to the forChild method of the RouterModule.
The route list is only responsible for providing additional routes and is intended for feature modules.
总是在特性路由模块中调用RouterModule.forChild。
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.
Now that you navigate to ContactComponent with the router, there's no reason to make it public.
Also, ContactComponent doesn't need a selector.
No template will ever again reference this ContactComponent.
It's gone from the AppComponent template.
The lazy-loaded HeroModule and CrisisModule follow the same principles as any feature module.
They don't look different from the eagerly loaded ContactModule.
HeroModule比CrisisModule略复杂一些,因此更适合用作范例。它的文件结构如下:
The HeroModule is a bit more complex than the CrisisModule, which makes it
a more interesting and useful example. Its file structure is as follows:
This is the child routing scenario familiar to readers of the
Child routing component section of the
Routing & Navigation 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.
Yet another HighlightDirective colors elements in yet a different shade.
In the next section, Shared modules, you'll resolve the repetition and inconsistencies.
HeroModule是特性模块,与其它的没什么不同。
The HeroModule is a feature module like any other.
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.
The app is shaping up.
But it carries three different versions of the HighlightDirective.
And the many files cluttering the app folder level could be better organized.
我们添加SharedModule来存放这些公共组件、指令和管道,并且共享给那些需要它们的模块。
Add a SharedModule to hold the common components, directives, and pipes
and share them with the modules that need them.
If you review the application, you may notice 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.
You can reduce the repetition by having SharedModule re-export CommonModule and FormsModule
so that importers of SharedModule get CommonModule and FormsModule for free.
As it happens, the components declared by SharedModule itself don't bind with [(ngModel)].
Technically, there is no need for SharedModule to import FormsModule.
Several components of the sample inject the UserService.
There should be only one instance of the UserService in the entire application
and only one provider of it.
UserService is an application-wide singleton.
You 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.
Do not specify app-wide singleton providers in a shared module.
A lazy-loaded module that imports that shared module makes its own copy of the service.
At the moment, the root folder is cluttered with the UserService
and TitleComponent that only appear in the root AppComponent.
You didn't include them in the SharedModule for reasons just explained.
The @NgModule metadata should be familiar.
You declare the TitleComponent because this module owns it and you export it
because AppComponent (which is in AppModule) displays the title in its template.
TitleComponent needs the Angular NgIf directive that you import from 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.
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 you decide to relocate the UserService file to the src/app/core folder.
Real-world apps have more to worry about.
They can have several single-use components (such as spinners, message toasts, and modal dialogs)
that appear only in the AppComponent template.
You 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.
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.
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 aren't shared.
We recommend collecting such single-use classes and hiding their details inside a CoreModule.
A simplified root AppModule imports CoreModule in its capacity as orchestrator of the application as a whole.
清理
Cleanup
我们已经重构完CoreModule和SharedModule,现在开始清理其它模块。
Having refactored to a CoreModule and a SharedModule, it's time to clean up the other modules.
清理 AppModule
A trimmer AppModule
这里是更新后的AppModule与其第三版本的对比:
Here is the updated AppModule paired with version 3 for comparison:
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 the following properties:
More precisely, Angular accumulates all imported providers before appending the items listed in @NgModule.providers.
This sequence ensures that whatever you add explicitly to the AppModule providers takes precedence
over the providers of imported modules.
现在添加CoreModule.forRoot方法,以便配置核心中的UserService。
Add a CoreModule.forRoot method that configures the core UserService.
You've extended the core UserService with an optional, injected UserServiceConfig.
If a UserServiceConfig exists, the UserService sets the user name from that config.
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 can produce a runtime error.
别忘了导入其返回结果,而且不要把它添加到@NgModule的其它任何列表中。
Remember to import the result; don't add it to any other @NgModule list.
You could hope that no developer makes that mistake.
Or you can guard against it and fail fast by adding the following CoreModule constructor.
constructor (@Optional()@SkipSelf() parentModule:CoreModule){if(parentModule){thrownewError('CoreModule is already loaded. Import it in the AppModule only');}}
这个构造函数会要求 Angular 把CoreModule注入自身。这看起来像一个危险的循环注入。
The constructor tells Angular to inject the CoreModule into itself.
That seems dangerously circular.
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."
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.
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.
总结
Conclusion
完工!你可以到下面的在线例子中试验它,并下载最终版本的全部源码。
You made it! You can examine and download the complete source for this final version from the live example.
Now that you understand NgModules, you may be interested
in the companion NgModule FAQs page
with its ready answers to specific design and implementation questions.