填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

NgModule FAQ

NgModules 可以幫你把應用組織成一些緊密相關的程式碼塊。

NgModules help organize an application into cohesive blocks of functionality.

這裡回答的是開發者常問起的關於 NgModule 的設計與實現問題。

This page answers the questions many developers ask about NgModule design and implementation.

我應該把哪些類別加到 declarations 中?

What classes should I add to the declarations array?

可宣告的類別(元件、指令和管道)新增到 declarations 列表中。

Add declarable classes—components, directives, and pipes—to a declarations list.

這些類別只能在應用程式的一個並且只有一個模組中宣告。 只有當它們從屬於某個模組時,才能把在模組中宣告它們。

Declare these classes in exactly one module of the application. Declare them in a module if they belong to that particular module.


什麼是可宣告的

What is a declarable?

可宣告的就是元件、指令和管道這些可以被加到模組的 declarations 列表中的類別。它們也是所有能被加到 declarations 中的類別。

Declarables are the class types—components, directives, and pipes—that you can add to a module's declarations list. They're the only classes that you can add to declarations.


哪些類別應該加到 declarations 中?

What classes should I not add to declarations?

只有可宣告的類別才能加到模組的 declarations 列表中。

Add only declarable classes to an NgModule's declarations list.

不要宣告:

Do not declare the following:

  • 已經在其它模組中宣告過的類別。無論它來自應用自己的模組(@NgModule)還是第三方模組。

    A class that's already declared in another module, whether an app module, @NgModule, or third-party module.

  • 從其它模組中匯入的指令。例如,不要宣告來自 @angular/forms 的 FORMS_DIRECTIVES,因為 FormsModule 已經宣告過它們了。

    An array of directives imported from another module. For example, don't declare FORMS_DIRECTIVES from @angular/forms because the FormsModule already declares it.

  • 模組類別。

    Module classes.

  • 服務類別

    Service classes.

  • 非 Angular 的類別和物件,比如:字串、數字、函式、實體模型、配置、業務邏輯和輔助類別。

    Non-Angular classes and objects, such as strings, numbers, functions, entity models, configurations, business logic, and helper classes.


為什麼要把同一個元件宣告在不同的 NgModule 屬性中?

Why list the same component in multiple NgModule properties?

AppComponent 經常被同時列在 declarationsbootstrap 中。 另外你還可能看到 HeroComponent 被同時列在 declarationsexportsentryComponent 中。

AppComponent is often listed in both declarations and bootstrap. You might see the same component listed in declarations, exports, and entryComponents.

看起來是多餘的,不過這些函式具有不同的功能,從它出現在一個列表中無法推斷出它也應該在另一個列表中。

While that seems redundant, these properties have different functions. Membership in one list doesn't imply membership in another list.

  • AppComponent 可能被宣告在此模組中,但可能不是引導元件。

    AppComponent could be declared in this module but not bootstrapped.

  • AppComponent 可能在此模組中引導,但可能是由另一個特性模組宣告的。

    AppComponent could be bootstrapped in this module but declared in a different feature module.

  • 某個元件可能是從另一個應用模組中匯入的(所以你沒法宣告它)並且被當前模組重新匯出。

    A component could be imported from another app module (so you can't declare it) and re-exported by this module.

  • 某個元件可能被匯出,以便用在外部元件的範本中,也可能同時被一個彈出式對話方塊載入。

    A component could be exported for inclusion in an external component's template as well as dynamically loaded in a pop-up dialog.


"Can't bind to 'x' since it isn't a known property of 'y'"是什麼意思?

What does "Can't bind to 'x' since it isn't a known property of 'y'" mean?

這個錯誤通常意味著你或者忘了宣告指令“x”,或者你沒有匯入“x”所屬的模組。

This error often means that you haven't declared the directive "x" or haven't imported the NgModule to which "x" belongs.

如果“x”其實不是屬性,或者是元件的私有屬性(比如它不帶 @Input@Output 裝飾器),那麼你也同樣會遇到這個錯誤。

Perhaps you declared "x" in an application sub-module but forgot to export it. The "x" class isn't visible to other modules until you add it to the exports list.


我應該匯入什麼?

What should I import?

匯入你需要在當前模組的元件範本中使用的那些公開的(被匯出的)可宣告類別

Import NgModules whose public (exported) declarable classes you need to reference in this module's component templates.

這意味著要從 @angular/common 中匯入 CommonModule 才能訪問 Angular 的內建指令,比如 NgIfNgFor。 你可以直接匯入它或者從重新匯出過該模組的其它模組中匯入它。

This always means importing CommonModule from @angular/common for access to the Angular directives such as NgIf and NgFor. You can import it directly or from another NgModule that re-exports it.

如果你的元件有 [(ngModel)] 雙向繫結表示式,就要從 @angular/forms 中匯入 FormsModule

Import FormsModule from @angular/forms if your components have [(ngModel)] two-way binding expressions.

如果當前模組中的元件包含了共享模組和特性模組中的元件、指令和管道,就匯入這些模組。

Import shared and feature modules when this module's components incorporate their components, directives, and pipes.

只能在根模組 AppModule匯入 BrowserModule

Import BrowserModule only in the root AppModule.


我應該匯入 BrowserModule 還是 CommonModule

Should I import BrowserModule or CommonModule?

幾乎所有要在瀏覽器中使用的應用的根模組AppModule)都應該從 @angular/platform-browser 中匯入 BrowserModule

The root application module, AppModule, of almost every browser application should import BrowserModule from @angular/platform-browser.

BrowserModule 提供了啟動和執行瀏覽器應用的那些基本的服務提供者。

BrowserModule provides services that are essential to launch and run a browser app.

BrowserModule 還從 @angular/common 中重新匯出了 CommonModule,這意味著 AppModule 中的元件也同樣可以訪問那些每個應用都需要的 Angular 指令,如 NgIfNgFor

BrowserModule also re-exports CommonModule from @angular/common, which means that components in the AppModule also have access to the Angular directives every app needs, such as NgIf and NgFor.

在其它任何模組中都不要匯入BrowserModule特性模組延遲載入模組應該改成匯入 CommonModule。 它們需要通用的指令。它們不需要重新初始化全應用級的提供者。

Do not import BrowserModule in any other module. Feature modules and lazy-loaded modules should import CommonModule instead. They need the common directives. They don't need to re-install the app-wide providers.

特性模組中匯入 CommonModule 可以讓它能用在任何目標平臺上,不僅是瀏覽器。那些跨平臺函式庫的作者應該喜歡這種方式的。

Importing CommonModule also frees feature modules for use on any target platform, not just browsers.


如果我兩次匯入同一個模組會怎麼樣?

What if I import the same module twice?

沒有任何問題。當三個模組全都匯入模組'A'時,Angular 只會首次遇到時載入一次模組'A',之後就不會這麼做了。

That's not a problem. When three modules all import Module 'A', Angular evaluates Module 'A' once, the first time it encounters it, and doesn't do so again.

無論 A 出現在所匯入模組的哪個層級,都會如此。 如果模組'B'匯入模組'A'、模組'C'匯入模組'B',模組'D'匯入 [C, B, A],那麼'D'會觸發模組'C'的載入,'C'會觸發'B'的載入,而'B'會載入'A'。 當 Angular 在'D'中想要獲取'B'和'A'時,這兩個模組已經被快取過了,可以立即使用。

That's true at whatever level A appears in a hierarchy of imported NgModules. When Module 'B' imports Module 'A', Module 'C' imports 'B', and Module 'D' imports [C, B, A], then 'D' triggers the evaluation of 'C', which triggers the evaluation of 'B', which evaluates 'A'. When Angular gets to the 'B' and 'A' in 'D', they're already cached and ready to go.

Angular 不允許模組之間出現迴圈依賴,所以不要讓模組'A'匯入模組'B',而模組'B'又匯入模組'A'。

Angular doesn't like NgModules with circular references, so don't let Module 'A' import Module 'B', which imports Module 'A'.


我應該匯出什麼?

What should I export?

匯出那些其它模組希望在自己的範本中參考的可宣告類別。這些也是你的公共類別。 如果你不匯出某個類別,它就是私有的,只對當前模組中宣告的其它元件可見。

Export declarable classes that components in other NgModules are able to reference in their templates. These are your public classes. If you don't export a declarable class, it stays private, visible only to other components declared in this NgModule.

可以匯出任何可宣告類別(元件、指令和管道),而不用管它是宣告在當前模組中還是某個匯入的模組中。

You can export any declarable class—components, directives, and pipes—whether it's declared in this NgModule or in an imported NgModule.

可以重新匯出整個匯入過的模組,這將導致重新匯出它們匯出的所有類別。重新匯出的模組甚至不用先匯入。

You can re-export entire imported NgModules, which effectively re-export all of their exported classes. An NgModule can even export a module that it doesn't import.


不應該匯出什麼?

What should I not export?

不要匯出:

Don't export the following:

  • 那些你只想在當前模組中宣告的那些元件中使用的私有元件、指令和管道。如果你不希望任何模組看到它,就不要匯出。

    Private components, directives, and pipes that you need only within components declared in this NgModule. If you don't want another NgModule to see it, don't export it.

  • 不可宣告的物件,比如服務、函式、配置、實體模型等。

    Non-declarable objects such as services, functions, configurations, and entity models.

  • 那些只被路由器或引導函式動態載入的元件。 比如入口元件可能從來不會在其它元件的範本中出現。 匯出它們沒有壞處,但也沒有好處。

    Components that are only loaded dynamically by the router or by bootstrapping. Such entry components can never be selected in another component's template. While there's no harm in exporting them, there's also no benefit.

  • 純服務模組沒有公開(匯出)的宣告。 例如,沒必要重新匯出 HttpClientModule,因為它不匯出任何東西。 它唯一的用途是一起把 http 的那些服務提供者新增到應用中。

    Pure service modules that don't have public (exported) declarations. For example, there's no point in re-exporting HttpClientModule because it doesn't export anything. Its only purpose is to add http service providers to the application as a whole.


我可以重新匯出類別和模組嗎?

Can I re-export classes and modules?

毫無疑問!

Absolutely.

模組是從其它模組中選取類別並把它們重新匯出成統一、便利的新模組的最佳方式。

NgModules are a great way to selectively aggregate classes from other NgModules and re-export them in a consolidated, convenience module.

模組可以重新匯出其它模組,這會導致重新匯出它們匯出的所有類別。 Angular 自己的 BrowserModule 就重新匯出了一組模組,例如:

An NgModule can re-export entire NgModules, which effectively re-exports all of their exported classes. Angular's own BrowserModule exports a couple of NgModules like this:

      
      exports: [CommonModule, ApplicationModule]
    

模組還能匯出一個組合,它可以包含自己的宣告、某些匯入的類別以及匯入的模組。

An NgModule can export a combination of its own declarations, selected imported classes, and imported NgModules.

不要費心去匯出純服務類別。 純服務類別的模組不會匯出任何可供其它模組使用的可宣告類別。 例如,不用重新匯出 HttpClientModule,因為它沒有匯出任何東西。 它唯一的用途是把那些 http 服務提供者一起新增到應用中。

Don't bother re-exporting pure service modules. Pure service modules don't export declarable classes that another NgModule could use. For example, there's no point in re-exporting HttpClientModule because it doesn't export anything. Its only purpose is to add http service providers to the application as a whole.


forRoot()方法是什麼?

What is the forRoot() method?

靜態方法 forRoot() 是一個約定,它可以讓開發人員更輕鬆的配置模組的想要單例使用的服務及其提供者。RouterModule.forRoot() 就是一個很好的例子。

The forRoot() static method is a convention that makes it easy for developers to configure services and providers that are intended to be singletons. A good example of forRoot() is the RouterModule.forRoot() method.

應用把一個 Routes 物件傳給 RouterModule.forRoot(),為的就是使用路由配置全應用級的 Router 服務。 RouterModule.forRoot() 返回一個ModuleWithProviders物件。 你把這個結果新增到根模組 AppModuleimports 列表中。

Apps pass a Routes array to RouterModule.forRoot() in order to configure the app-wide Router service with routes. RouterModule.forRoot() returns a ModuleWithProviders. You add that result to the imports list of the root AppModule.

只能在應用的根模組 AppModule 中呼叫並匯入 forRoot() 的結果。 在其它模組,特別是延遲載入模組中,不要匯入它。 要了解關於 forRoot() 的更多資訊,參閱單例服務一章的 the forRoot() 模式部分。

Only call and import a forRoot() result in the root application module, AppModule. Avoid importing it in any other module, particularly in a lazy-loaded module. For more information on forRoot() see the forRoot() pattern section of the Singleton Services guide.

對於服務來說,除了可以使用 forRoot() 外,更好的方式是在該服務的 @Injectable() 裝飾器中指定 providedIn: 'root',它讓該服務自動在全應用級可用,這樣它也就預設是單例的。

For a service, instead of using forRoot(), specify providedIn: 'root' on the service's @Injectable() decorator, which makes the service automatically available to the whole application and thus singleton by default.

RouterModule 也提供了靜態方法 forChild(),用於配置延遲載入模組的路由。

RouterModule also offers a forChild() static method for configuring the routes of lazy-loaded modules.

forRoot()forChild() 都是約定俗成的方法名,它們分別用於在根模組和特性模組中配置服務。

forRoot() and forChild() are conventional names for methods that configure services in root and feature modules respectively.

當你寫類似的需要可配置的服務提供者時,請遵循這個約定。

Follow this convention when you write similar modules with configurable service providers.


為什麼服務提供者在特性模組中的任何地方都是可見的?

Why is a service provided in a feature module visible everywhere?

列在引導模組的 @NgModule.providers 中的服務提供者具有全應用級作用域。 往 NgModule.providers 中新增服務提供者將導致該服務被發佈到整個應用中。

Providers listed in the @NgModule.providers of a bootstrapped module have application scope. Adding a service provider to @NgModule.providers effectively publishes the service to the entire application.

當你匯入一個模組時,Angular 就會把該模組的服務提供者(也就是它的 providers 列表中的內容)加入該應用的根注入器中。

When you import an NgModule, Angular adds the module's service providers (the contents of its providers list) to the application root injector.

這會讓該提供者對應用中所有知道該提供者令牌(token)的類別都可見。

This makes the provider visible to every class in the application that knows the provider's lookup token, or name.

透過 NgModule 匯入來實現可擴充套件性是 NgModule 體系的主要設計目標。 把 NgModule 的提供者併入應用程式的注入器可以讓函式庫模組使用新的服務來強化應用程式變得更容易。 只要新增一次 HttpClientModule,那麼應用中的每個元件就都可以發起 Http 請求了。

Extensibility through NgModule imports is a primary goal of the NgModule system. Merging NgModule providers into the application injector makes it easy for a module library to enrich the entire application with new services. By adding the HttpClientModule once, every application component can make HTTP requests.

不過,如果你期望模組的服務只對那個特性模組內部宣告的元件可見,那麼這可能會帶來一些不受歡迎的意外。 如果 HeroModule 提供了一個 HeroService,並且根模組 AppModule 匯入了 HeroModule,那麼任何知道 HeroService型別的類別都可能注入該服務,而不僅是在 HeroModule 中宣告的那些類別。

However, this might feel like an unwelcome surprise if you expect the module's services to be visible only to the components declared by that feature module. If the HeroModule provides the HeroService and the root AppModule imports HeroModule, any class that knows the HeroService type can inject that service, not just the classes declared in the HeroModule.

要限制對某個服務的訪問,可以考慮延遲載入提供該服務的 NgModule。參閱我要如何把服務的範圍限定為某個模組?

To limit access to a service, consider lazy loading the NgModule that provides that service. See How do I restrict service scope to a module? for more information.


為什麼在延遲載入模組中宣告的服務提供者只對該模組自身可見?

Why is a service provided in a lazy-loaded module visible only to that module?

和啟動時就載入的模組中的提供者不同,延遲載入模組中的提供者是侷限於模組的。

Unlike providers of the modules loaded at launch, providers of lazy-loaded modules are module-scoped.

當 Angular 路由器延遲載入一個模組時,它建立了一個新的執行環境。 那個環境擁有自己的注入器,它是應用注入器的直屬子級。

When the Angular router lazy-loads a module, it creates a new execution context. That context has its own injector, which is a direct child of the application injector.

路由器把該延遲載入模組的提供者和它匯入的模組的提供者新增到這個子注入器中。

The router adds the lazy module's providers and the providers of its imported NgModules to this child injector.

這些提供者不會被擁有相同令牌的應用級別提供者的變化所影響。 當路由器在延遲載入環境中建立元件時,Angular 優先使用延遲載入模組中的服務實例,而不是來自應用的根注入器的。

These providers are insulated from changes to application providers with the same lookup token. When the router creates a component within the lazy-loaded context, Angular prefers service instances created from these providers to the service instances of the application root injector.


如果兩個模組提供了同一個服務會怎麼樣?

What if two modules provide the same service?

當同時載入了兩個匯入的模組,它們都列出了使用同一個令牌的提供者時,後匯入的模組會“獲勝”,這是因為這兩個提供者都被新增到了同一個注入器中。

When two imported modules, loaded at the same time, list a provider with the same token, the second module's provider "wins". That's because both providers are added to the same injector.

當 Angular 嘗試根據令牌注入服務時,它使用第二個提供者來建立並交付服務實例。

When Angular looks to inject a service for that token, it creates and delivers the instance created by the second provider.

每個注入了該服務的類別獲得的都是由第二個提供者建立的實例。 即使是宣告在第一個模組中的類別,它取得的實例也是來自第二個提供者的。

Every class that injects this service gets the instance created by the second provider. Even classes declared within the first module get the instance created by the second provider.

如果模組 A 提供了一個使用令牌'X'的服務,並且匯入的模組 B 也用令牌'X'提供了一個服務,那麼模組 A 中定義的服務“獲勝”了。

If NgModule A provides a service for token 'X' and imports an NgModule B that also provides a service for token 'X', then NgModule A's service definition "wins".

由根 AppModule 提供的服務相對於所匯入模組中提供的服務有優先權。換句話說:AppModule 總會獲勝。

The service provided by the root AppModule takes precedence over services provided by imported NgModules. The AppModule always wins.


我應該如何把服務的範圍限制到模組中?

How do I restrict service scope to a module?

如果一個模組在應用程式啟動時就載入,它的 @NgModule.providers 具有全應用級作用域。 它們也可用於整個應用的注入中。

When a module is loaded at application launch, its @NgModule.providers have application-wide scope; that is, they are available for injection throughout the application.

匯入的提供者很容易被由其它匯入模組中的提供者替換掉。 這雖然是故意這樣設計的,但是也可能引起意料之外的結果。

Imported providers are easily replaced by providers from another imported NgModule. Such replacement might be by design. It could be unintentional and have adverse consequences.

作為一個通用的規則,應該只匯入一次帶提供者的模組,最好在應用的根模組中。 那裡也是配置、包裝和改寫這些服務的最佳位置。

As a general rule, import modules with providers exactly once, preferably in the application's root module. That's also usually the best place to configure, wrap, and override them.

假設模組需要一個訂製過的 HttpBackend,它為所有的 Http 請求新增一個特別的請求頭。 如果應用中其它地方的另一個模組也訂製了 HttpBackend 或僅僅匯入了 HttpClientModule,它就會改寫當前模組的 HttpBackend 提供者,丟掉了這個特別的請求頭。 這樣伺服器就會拒絕來自該模組的請求。

Suppose a module requires a customized HttpBackend that adds a special header for all Http requests. If another module elsewhere in the application also customizes HttpBackend or merely imports the HttpClientModule, it could override this module's HttpBackend provider, losing the special header. The server will reject http requests from this module.

要消除這個問題,就只能在應用的根模組 AppModule 中匯入 HttpClientModule

To avoid this problem, import the HttpClientModule only in the AppModule, the application root module.

如果你必須防範這種“提供者腐化”現象,那就不要依賴於“啟動時載入”模組的 providers

If you must guard against this kind of "provider corruption", don't rely on a launch-time module's providers.

只要可能,就讓模組延遲載入。 Angular 給了延遲載入模組自己的子注入器。 該模組中的提供者只對由該注入器建立的元件樹可見。

Load the module lazily if you can. Angular gives a lazy-loaded module its own child injector. The module's providers are visible only within the component tree created with this injector.

如果你必須在應用程式啟動時主動載入該模組,就改成在元件中提供該服務

If you must load the module eagerly, when the application starts, provide the service in a component instead.

繼續看這個例子,假設某個模組的元件真的需要一個私有的、自訂的 HttpBackend

Continuing with the same example, suppose the components of a module truly require a private, custom HttpBackend.

那就建立一個“最上層元件”來扮演該模組中所有元件的根。 把這個自訂的 HttpBackend 提供者新增到這個最上層元件的 providers 列表中,而不是該模組的 providers 中。 回憶一下,Angular 會為每個元件實例建立一個子注入器,並使用元件自己的 providers 來配置這個注入器。

Create a "top component" that acts as the root for all of the module's components. Add the custom HttpBackend provider to the top component's providers list rather than the module's providers. Recall that Angular creates a child injector for each component instance and populates the injector with the component's own providers.

當該元件的子元件想要一個 HttpBackend 服務時,Angular 會提供一個區域性的 HttpBackend 服務,而不是應用的根注入器建立的那個。 子元件將正確發起 http 請求,而不管其它模組對 HttpBackend 做了什麼。

When a child of this component asks for the HttpBackend service, Angular provides the local HttpBackend service, not the version provided in the application root injector. Child components make proper HTTP requests no matter what other modules do to HttpBackend.

確保把模組中的元件都建立成這個最上層元件的子元件。

Be sure to create module components as children of this module's top component.

你可以把這些子元件都嵌在最上層元件的範本中。或者,給最上層元件一個 <router-outlet>,讓它作為路由的宿主。 定義子路由,並讓路由器把模組中的元件載入進該路由出口(outlet)中。

You can embed the child components in the top component's template. Alternatively, make the top component a routing host by giving it a <router-outlet>. Define child routes and let the router load module components into that outlet.

雖然透過在延遲載入模組中或元件中提供某個服務來限制它的訪問都是可行的方式,但在元件中提供服務可能導致這些服務出現多個實例。因此,應該優先使用延遲載入的方式。

Though you can limit access to a service by providing it in a lazy loaded module or providing it in a component, providing services in a component can lead to multiple instances of those services. Thus, the lazy loading is preferable.


我應該把全應用級提供者新增到根模組 AppModule 中還是根元件 AppComponent 中?

Should I add application-wide providers to the root AppModule or the root AppComponent?

透過在服務的 @Injectable() 裝飾器中(例如服務)指定 providedIn: 'root' 來定義全應用級提供者,或者 InjectionToken 的構造器(例如提供令牌的地方),都可以定義全應用級提供者。 透過這種方式建立的服務提供者會自動在整個應用中可用,而不用把它列在任何模組中。

Define application-wide providers by specifying providedIn: 'root' on its @Injectable() decorator (in the case of services) or at InjectionToken construction (in the case where tokens are provided). Providers that are created this way automatically are made available to the entire application and don't need to be listed in any module.

如果某個提供者不能用這種方式配置(可能因為它沒有有意義的預設值),那就在根模組 AppModule 中註冊這些全應用級服務,而不是在 AppComponent 中。

If a provider cannot be configured in this way (perhaps because it has no sensible default value), then register application-wide providers in the root AppModule, not in the AppComponent.

延遲載入模組及其元件可以注入 AppModule 中的服務,卻不能注入 AppComponent 中的。

Lazy-loaded modules and their components can inject AppModule services; they can't inject AppComponent services.

只有當該服務必須對 AppComponent 元件樹之外的元件不可見時,才應該把服務註冊進 AppComponentproviders 中。 這是一個非常罕見的異常用法。

Register a service in AppComponent providers only if the service must be hidden from components outside the AppComponent tree. This is a rare use case.

更一般地說,優先把提供者註冊進模組中,而不是元件中。

More generally, prefer registering providers in NgModules to registering in components.

討論

Discussion

Angular 把所有啟動期模組的提供者都註冊進了應用的根注入器中。 這些服務是由根注入器中的提供者建立的,並且在整個應用中都可用。 它們具有應用級作用域

Angular registers all startup module providers with the application root injector. The services that root injector providers create have application scope, which means they are available to the entire application.

某些服務(比如 Router)只有當註冊進應用的根注入器時才能正常工作。

Certain services, such as the Router, only work when you register them in the application root injector.

相反,Angular 使用 AppComponent 自己的注入器註冊了 AppComponent 的提供者。 AppComponent 服務只在該元件及其子元件樹中才能使用。 它們具有元件級作用域

By contrast, Angular registers AppComponent providers with the AppComponent's own injector. AppComponent services are available only to that component and its component tree. They have component scope.

AppComponent 的注入器是根注入器的子級,注入器層次中的下一級。 這對於沒有路由器的應用來說幾乎是整個應用了。 但對那些帶路由的應用,路由操作位於最上層,那裡不存在 AppComponent 服務。這意味著延遲載入模組不能使用它們。

The AppComponent's injector is a child of the root injector, one down in the injector hierarchy. For applications that don't use the router, that's almost the entire application. But in routed applications, routing operates at the root level where AppComponent services don't exist. This means that lazy-loaded modules can't reach them.


我應該把其它提供者註冊到模組中還是元件中?

Should I add other providers to a module or a component?

提供者應該使用 @Injectable 語法進行配置。只要可能,就應該把它們在應用的根注入器中提供(providedIn: 'root')。 如果它們只被延遲載入的上下文中使用,那麼這種方式配置的服務就是延遲載入的。

Providers should be configured using @Injectable syntax. If possible, they should be provided in the application root (providedIn: 'root'). Services that are configured this way are lazily loaded if they are only used from a lazily loaded context.

如果要由消費方來決定是否把它作為全應用級提供者,那麼就要在模組中(@NgModule.providers)註冊提供者,而不是元件中(@Component.providers)。

If it's the consumer's decision whether a provider is available application-wide or not, then register providers in modules (@NgModule.providers) instead of registering in components (@Component.providers).

當你必須把服務實例的範圍限制到某個元件及其子元件樹時,就把提供者註冊到該元件中。 指令的提供者也同樣照此處理。

Register a provider with a component when you must limit the scope of a service instance to that component and its component tree. Apply the same reasoning to registering a provider with a directive.

例如,如果英雄編輯元件需要自己私有的快取英雄服務實例,那就應該把 HeroService 註冊進 HeroEditorComponent 中。 這樣,每個新的 HeroEditorComponent 的實例都會得到一份自己的快取服務實例。 編輯器的改動只會作用於它自己的服務,而不會影響到應用中其它地方的英雄實例。

For example, an editing component that needs a private copy of a caching service should register the service with the component. Then each new instance of the component gets its own cached service instance. The changes that editor makes in its service don't touch the instances elsewhere in the application.

總是在根模組 AppModule 中註冊全應用級服務,而不要在根元件 AppComponent 中。

Always register application-wide services with the root AppModule, not the root AppComponent.


為什麼在共享模組中為延遲載入模組提供服務是個餿主意?

Why is it bad if a shared module provides a service to a lazy-loaded module?

急性載入的場景

The eagerly loaded scenario

當急性載入的模組提供了服務時,比如 UserService,該服務是在全應用級可用的。如果根模組提供了 UserService,並匯入了另一個也提供了同一個 UserService 的模組,Angular 就會把它們中的一個註冊進應用的根注入器中(參閱如果兩次匯入了同一個模組會怎樣?)。

When an eagerly loaded module provides a service, for example a UserService, that service is available application-wide. If the root module provides UserService and imports another module that provides the same UserService, Angular registers one of them in the root app injector (see What if I import the same module twice?).

然後,當某些元件注入 UserService 時,Angular 就會發現它已經在應用的根注入器中了,並交付這個全應用級的單例服務。這樣不會出現問題。

Then, when some component injects UserService, Angular finds it in the app root injector, and delivers the app-wide singleton service. No problem.

延遲載入場景

The lazy loaded scenario

現在,考慮一個延遲載入的模組,它也提供了一個名叫 UserService 的服務。

Now consider a lazy loaded module that also provides a service called UserService.

當路由器準備延遲載入 HeroModule 的時候,它會建立一個子注入器,並且把 UserService 的提供者註冊到那個子注入器中。子注入器和根注入器是不同的。

When the router lazy loads a module, it creates a child injector and registers the UserService provider with that child injector. The child injector is not the root injector.

當 Angular 建立一個延遲載入的 HeroComponent 時,它必須注入一個 UserService。 這次,它會從延遲載入模組的子注入器中查詢 UserService 的提供者,並用它建立一個 UserService 的新實例。 這個 UserService 實例與 Angular 在主動載入的元件中注入的那個全應用級單例物件截然不同。

When Angular creates a lazy component for that module and injects UserService, it finds a UserService provider in the lazy module's child injector and creates a new instance of the UserService. This is an entirely different UserService instance than the app-wide singleton version that Angular injected in one of the eagerly loaded components.

這個場景導致你的應用每次都建立一個新的服務實例,而不是使用單例的服務。

This scenario causes your app to create a new instance every time, instead of using the singleton.


為什麼延遲載入模組會建立一個子注入器?

Why does lazy loading create a child injector?

Angular 會把 @NgModule.providers 中的提供者新增到應用的根注入器中…… 除非該模組是延遲載入的,這種情況下,Angular 會建立一子注入器,並且把該模組的提供者新增到這個子注入器中。

Angular adds @NgModule.providers to the application root injector, unless the NgModule is lazy-loaded. For a lazy-loaded NgModule, Angular creates a child injector and adds the module's providers to the child injector.

這意味著模組的行為將取決於它是在應用啟動期間載入的還是後來延遲載入的。如果疏忽了這一點,可能導致嚴重後果

This means that an NgModule behaves differently depending on whether it's loaded during application start or lazy-loaded later. Neglecting that difference can lead to adverse consequences.

為什麼 Angular 不能像主動載入模組那樣把延遲載入模組的提供者也新增到應用程式的根注入器中呢?為什麼會出現這種不一致?

Why doesn't Angular add lazy-loaded providers to the app root injector as it does for eagerly loaded NgModules?

歸根結底,這來自於 Angular 依賴注入系統的一個基本特徵: 在注入器還沒有被第一次使用之前,可以不斷為其新增提供者。 一旦注入器已經建立和開始交付服務,它的提供者列表就被凍結了,不再接受新的提供者。

The answer is grounded in a fundamental characteristic of the Angular dependency-injection system. An injector can add providers until it's first used. Once an injector starts creating and delivering services, its provider list is frozen; no new providers are allowed.

當應用啟動時,Angular 會首先使用所有主動載入模組中的提供者來配置根注入器,這發生在它建立第一個元件以及注入任何服務之前。 一旦應用開始工作,應用的根注入器就不再接受新的提供者了。

When an applications starts, Angular first configures the root injector with the providers of all eagerly loaded NgModules before creating its first component and injecting any of the provided services. Once the application begins, the app root injector is closed to new providers.

之後,應用邏輯開始延遲載入某個模組。 Angular 必須把這個延遲載入模組中的提供者新增到某個注入器中。 但是它無法將它們新增到應用的根注入器中,因為根注入器已經不再接受新的提供者了。 於是,Angular 在延遲載入模組的上下文中建立了一個新的子注入器。

Time passes and application logic triggers lazy loading of an NgModule. Angular must add the lazy-loaded module's providers to an injector somewhere. It can't add them to the app root injector because that injector is closed to new providers. So Angular creates a new child injector for the lazy-loaded module context.


我要如何知道一個模組或服務是否已經載入過了?

How can I tell if an NgModule or service was previously loaded?

某些模組及其服務只能被根模組 AppModule 載入一次。 在延遲載入模組中再次匯入這個模組會導致錯誤的行為,這個錯誤可能非常難於檢測和診斷。

Some NgModules and their services should be loaded only once by the root AppModule. Importing the module a second time by lazy loading a module could produce errant behavior that may be difficult to detect and diagnose.

為了防範這種風險,可以寫一個建構函式,它會嘗試從應用的根注入器中注入該模組或服務。如果這種注入成功了,那就說明這個類別是被第二次載入的,你就可以丟擲一個錯誤,或者採取其它挽救措施。

To prevent this issue, write a constructor that attempts to inject the module or service from the root app injector. If the injection succeeds, the class has been loaded a second time. You can throw an error or take other remedial action.

某些 NgModule(例如 BrowserModule)就實現了那樣一個守衛。 下面是一個名叫 GreetingModule 的 NgModule 的 自訂建構函式。

Certain NgModules, such as BrowserModule, implement such a guard. Here is a custom constructor for an NgModule called GreetingModule.

constructor(@Optional() @SkipSelf() parentModule?: GreetingModule) { if (parentModule) { throw new Error( 'GreetingModule is already loaded. Import it in the AppModule only'); } }
src/app/greeting/greeting.module.ts (Constructor)
      
      constructor(@Optional() @SkipSelf() parentModule?: GreetingModule) {
  if (parentModule) {
    throw new Error(
      'GreetingModule is already loaded. Import it in the AppModule only');
  }
}
    

什麼是入口元件

What is an entry component?

Angular 根據元件型別命令式載入的元件是入口元件.

An entry component is any component that Angular loads imperatively by type.

而透過元件選擇器宣告式載入的元件則不是入口元件。

A component loaded declaratively via its selector is not an entry component.

Angular 會宣告式的載入元件,它使用元件的選擇器在範本中定位元素。 然後,Angular 會建立該元件的 HTML 表示,並把它插入 DOM 中所選元素的內部。它們不是入口元件。

Angular loads a component declaratively when using the component's selector to locate the element in the template. Angular then creates the HTML representation of the component and inserts it into the DOM at the selected element. These aren't entry components.

而用於引導的根 AppComponent 則是一個入口元件。 雖然它的選擇器匹配了 index.html 中的一個元素,但是 index.html 並不是元件範本,而且 AppComponent 選擇器也不會在任何元件範本中出現。

The bootstrapped root AppComponent is an entry component. True, its selector matches an element tag in index.html. But index.html isn't a component template and the AppComponent selector doesn't match an element in any component template.

在路由定義中用到的元件也同樣是入口元件。 路由定義根據型別來參考元件。 路由器會忽略路由元件的選擇器(即使它有選擇器),並且把該元件動態載入到 RouterOutlet 中。

Components in route definitions are also entry components. A route definition refers to a component by its type. The router ignores a routed component's selector, if it even has one, and loads the component dynamically into a RouterOutlet.

要了解更多,參閱入口元件一章。

For more information, see Entry Components.


引導元件入口元件有什麼不同?

What's the difference between a bootstrap component and an entry component?

引導元件是入口元件的一種。 它是被 Angular 的引導(應用啟動)過程載入到 DOM 中的入口元件。 其它入口元件則是被其它方式動態載入的,比如被路由器載入。

A bootstrapped component is an entry component that Angular loads into the DOM during the bootstrap process (application launch). Other entry components are loaded dynamically by other means, such as with the router.

@NgModule.bootstrap 屬性告訴編譯器這是一個入口元件,同時它應該產生一些程式碼來用該元件引導此應用。

The @NgModule.bootstrap property tells the compiler that this is an entry component and it should generate code to bootstrap the application with this component.

不需要把元件同時列在 bootstrapentryComponent 列表中 —— 雖然這樣做也沒壞處。

There's no need to list a component in both the bootstrap and entryComponents lists, although doing so is harmless.

要了解更多,參閱入口元件一章。

For more information, see Entry Components.


什麼時候我應該把元件加到 entryComponents 中?

When do I add components to entryComponents?

大多數應用開發者都不需要把元件新增到 entryComponents 中。

Most application developers won't need to add components to the entryComponents.

Angular 會自動把恰當的元件新增到入口元件中。 列在 @NgModule.bootstrap 中的元件會自動加入。 由路由配置參考到的元件會被自動加入。 用這兩種機制新增的元件在入口元件中佔了絕大多數。

Angular adds certain components to entry components automatically. Components listed in @NgModule.bootstrap are added automatically. Components referenced in router configuration are added automatically. These two mechanisms account for almost all entry components.

如果你的應用要用其它手段來根據型別引導或動態載入元件,那就得把它顯式新增到 entryComponents 中。

If your app happens to bootstrap or dynamically load a component by type in some other manner, you must add it to entryComponents explicitly.

雖然把元件加到這個列表中也沒什麼壞處,不過最好還是只新增真正的入口元件。 不要新增那些被其它元件的範本參考過的元件。

Although it's harmless to add components to this list, it's best to add only the components that are truly entry components. Don't include components that are referenced in the templates of other components.

要了解更多,參閱入口元件一章。

For more information, see Entry Components.


為什麼 Angular 需要入口元件

Why does Angular need entryComponents?

原因在於搖樹優化。對於產品化應用,你會希望載入儘可能小而快的程式碼。 程式碼中應該僅僅包括那些實際用到的類別。 它應該排除那些從未用過的元件,無論該元件是否被宣告過。

The reason is tree shaking. For production apps you want to load the smallest, fastest code possible. The code should contain only the classes that you actually need. It should exclude a component that's never used, whether or not that component is declared.

事實上,大多數函式庫中宣告和匯出的元件你都用不到。 如果你從未參考它們,那麼搖樹優化器就會從最終的程式碼套件中把這些元件砍掉。

In fact, many libraries declare and export components you'll never use. If you don't reference them, the tree shaker drops these components from the final code package.

如果Angular 編譯器為每個宣告的元件都生成了程式碼,那麼搖樹優化器的作用就沒有了。

If the Angular compiler generated code for every declared component, it would defeat the purpose of the tree shaker.

所以,編譯器轉而採用一種遞迴策略,它只為你用到的那些元件產生程式碼。

Instead, the compiler adopts a recursive strategy that generates code only for the components you use.

編譯器從入口元件開始工作,為它在入口元件的範本中找到的那些元件產生程式碼,然後又為在這些元件中的範本中發現的元件產生程式碼,以此類推。 當這個過程結束時,它就已經為每個入口元件以及從入口元件可以抵達的每個元件生成了程式碼。

The compiler starts with the entry components, then it generates code for the declared components it finds in an entry component's template, then for the declared components it discovers in the templates of previously compiled components, and so on. At the end of the process, the compiler has generated code for every entry component and every component reachable from an entry component.

如果該元件不是入口元件或者沒有在任何範本中發現過,編譯器就會忽略它。

If a component isn't an entry component or wasn't found in a template, the compiler omits it.


有哪些型別的模組?我應該如何使用它們?

What kinds of modules should I have and how should I use them?

每個應用都不一樣。根據不同程度的經驗,開發者會做出不同的選擇。下列建議和指導原則廣受歡迎。

Every app is different. Developers have various levels of experience and comfort with the available choices. Some suggestions and guidelines appear to have wide appeal.

SharedModule

為那些可能會在應用中到處使用的元件、指令和管道建立 SharedModule。 這種模組應該只包含 declarations,並且應該匯出幾乎所有 declarations 裡面的宣告。

SharedModule is a conventional name for an NgModule with the components, directives, and pipes that you use everywhere in your app. This module should consist entirely of declarations, most of them exported.

SharedModule 可以重新匯出其它小部件模組,比如 CommonModuleFormsModule 和提供你廣泛使用的 UI 控制元件的那些模組。

The SharedModule may re-export other widget modules, such as CommonModule, FormsModule, and NgModules with the UI controls that you use most widely.

SharedModule不應該帶有 providers,原因在前面解釋過了。 它的匯入或重新匯出的模組中也不應該有 providers。 如果你要違背這條指導原則,請務必想清楚你在做什麼,並要有充分的理由。

The SharedModule should not have providers for reasons explained previously. Nor should any of its imported or re-exported modules have providers.

在任何特性模組中(無論是你在應用啟動時主動載入的模組還是之後延遲載入的模組),你都可以隨意匯入這個 SharedModule

Import the SharedModule in your feature modules, both those loaded when the app starts and those you lazy load later.

特性模組

Feature Modules

特性模組是你圍繞特定的應用業務領域建立的模組,比如使用者工作流、小工具集等。它們包含指定的特性,並為你的應用提供支援,比如路由、服務、視窗部件等。 要對你的應用中可能會有哪些特性模組有個概念,考慮如果你要把與特定功能(比如搜尋)有關的檔案放進一個目錄下,該目錄的內容就可能是一個名叫 SearchModule 的特性模組。 它將會包含構成搜尋功能的全部元件、路由和範本。

Feature modules are modules you create around specific application business domains, user workflows, and utility collections. They support your app by containing a particular feature, such as routes, services, widgets, etc. To conceptualize what a feature module might be in your app, consider that if you would put the files related to a certain functionality, like a search, in one folder, that the contents of that folder would be a feature module that you might call your SearchModule. It would contain all of the components, routing, and templates that would make up the search functionality.

要了解更多,參閱特性模組模組的分類

For more information, see Feature Modules and Module Types

在 NgModule 和 JavaScript 模組之間有什麼不同?

What's the difference between NgModules and JavaScript Modules?

在 Angular 應用中,NgModule 會和 JavaScript 的模組一起工作。

In an Angular app, NgModules and JavaScript modules work together.

在現代 JavaScript 中,每個檔案都是模組(參閱模組)。 在每個檔案中,你要寫一個 export 語句將模組的一部分公開。

In modern JavaScript, every file is a module (see the Modules page of the Exploring ES6 website). Within each file you write an export statement to make parts of the module public.

Angular 模組是一個帶有 @NgModule 裝飾器的類別,而 JavaScript 模組則沒有。 Angular 的 NgModule 有自己的 importsexports 來達到類似的目的。

An Angular NgModule is a class with the @NgModule decorator—JavaScript modules don't have to have the @NgModule decorator. Angular's NgModule has imports and exports and they serve a similar purpose.

你可以匯入其它 NgModules,以便在當前模組的元件範本中使用它們匯出的類別。 你可以匯出當前 NgModules 中的類別,以便其它 NgModules 可以匯入它們,並用在自己的元件範本中。

You import other NgModules so you can use their exported classes in component templates. You export this NgModule's classes so they can be imported and used by components of other NgModules.

要了解更多,參閱 JavaScript 模組 vs. NgModules 一章

For more information, see JavaScript Modules vs. NgModules.


Angular 如何查詢範本中的元件、指令和管道?什麼是 範本參考

How does Angular find components, directives, and pipes in a template?
What is a template reference?

Angular 編譯器在元件範本內查詢其它元件、指令和管道。一旦找到了,那就是一個“範本參考”。

The Angular compiler looks inside component templates for other components, directives, and pipes. When it finds one, that's a template reference.

Angular 編譯器透過在一個範本的 HTML 中匹配元件或指令的選擇器(selector),來查詢元件或指令。

The Angular compiler finds a component or directive in a template when it can match the selector of that component or directive to some HTML in that template.

編譯器透過分析範本 HTML 中的管道語法中是否出現了特定的管道名來查詢對應的管道。

The compiler finds a pipe if the pipe's name appears within the pipe syntax of the template HTML.

Angular 只查詢兩種元件、指令或管道:1)那些在當前模組中宣告過的,以及 2)那些被當前模組匯入的模組所匯出的。

Angular only matches selectors and pipe names for classes that are declared by this module or exported by a module that this module imports.


什麼是 Angular 編譯器?

What is the Angular compiler?

Angular 編譯器會把你所編寫的應用程式碼轉換成高效能的 JavaScript 程式碼。 在編譯過程中,@NgModule 的元資料扮演了很重要的角色。

The Angular compiler converts the application code you write into highly performant JavaScript code. The @NgModule metadata plays an important role in guiding the compilation process.

你寫的程式碼是無法直接執行的。 比如元件。 元件有一個範本,其中包含了自訂元素、屬性型指令、Angular 繫結宣告和一些顯然不屬於原生 HTML 的古怪語法。

The code you write isn't immediately executable. For example, components have templates that contain custom elements, attribute directives, Angular binding declarations, and some peculiar syntax that clearly isn't native HTML.

Angular 編譯器讀取範本的 HTML,把它和相應的元件類別程式碼組合在一起,併產出元件工廠

The Angular compiler reads the template markup, combines it with the corresponding component class code, and emits component factories.

元件工廠為元件建立純粹的、100% JavaScript 的表示形式,它包含了 @Component 元資料中描述的一切:HTML、繫結指令、附屬的樣式等……

A component factory creates a pure, 100% JavaScript representation of the component that incorporates everything described in its @Component metadata: the HTML, the binding instructions, the attached styles.

由於指令管道都出現在元件範本中,*Angular 編譯器**也同樣會把它們組合進編譯後的元件程式碼中。

Because directives and pipes appear in component templates, the Angular compiler incorporates them into compiled component code too.

@NgModule 元資料告訴Angular 編譯器要為當前模組編譯哪些元件,以及如何把當前模組和其它模組連結起來。

@NgModule metadata tells the Angular compiler what components to compile for this module and how to link this module with other modules.