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

在模組中提供依賴

Providing dependencies in modules

提供者就是一本說明書,用來指導依賴注入系統該如何獲取某個依賴的值。 大多數情況下,這些依賴就是你要建立和提供的那些服務。

A provider is an instruction to the Dependency Injection system on how to obtain a value for a dependency. Most of the time, these dependencies are services that you create and provide.

要想檢視本頁提到的這個帶有特性模組的範例應用,參閱現場演練 / 下載範例

For the final sample app using the provider that this page describes, see the現場演練 / 下載範例.

提供服務

Providing a service

如果你是用 Angular CLI 建立的應用,那麼可以使用下列 CLI 的 ng generate命令在專案根目錄下建立一個服務。把其中的 User 替換成你的服務名。

If you already have an app that was created with the Angular CLI, you can create a service using the ng generateCLI command in the root project directory. Replace User with the name of your service.

ng generate service User
      
      ng generate service User
    

該命令會建立下列 UserService 骨架:

This command creates the following UserService skeleton:

import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class UserService { }
src/app/user.service.ts
      
      import { Injectable } from '@angular/core';

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

現在,你就可以在應用中到處注入 UserService 了。

You can now inject UserService anywhere in your application.

該服務本身是 CLI 建立的一個類別,並且加上了 @Injectable() 裝飾器。預設情況下,該裝飾器是用 providedIn 屬性進行配置的,它會為該服務建立一個提供者。在這個例子中,providedIn: 'root' 指定 Angular 應該在根注入器中提供該服務。

The service itself is a class that the CLI generated and that's decorated with @Injectable(). By default, this decorator has a providedIn property, which creates a provider for the service. In this case, providedIn: 'root' specifies that Angular should provide the service in the root injector.

提供者的作用域

Provider scope

當你把服務提供者新增到應用的根注入器中時,它就在整個應用程式中可用了。 另外,這些服務提供者也同樣對整個應用中的類別是可用的 —— 只要它們有供查詢用的服務令牌。

When you add a service provider to the root application injector, it’s available throughout the app. Additionally, these providers are also available to all the classes in the app as long they have the lookup token.

你應該始終在根注入器中提供這些服務 —— 除非你希望該服務只有在消費方要匯入特定的 @NgModule 時才生效。

You should always provide your service in the root injector unless there is a case where you want the service to be available only if the consumer imports a particular @NgModule.

providedIn 與 NgModule

providedIn and NgModules

也可以規定某個服務只有在特定的 @NgModule 中提供。比如,如果你希望只有當消費方匯入了你建立的 UserModule 時才讓 UserService 在應用中生效,那就可以指定該服務要在該模組中提供:

It's also possible to specify that a service should be provided in a particular @NgModule. For example, if you don't want UserService to be available to applications unless they import a UserModule you've created, you can specify that the service should be provided in the module:

import { Injectable } from '@angular/core'; import { UserModule } from './user.module'; @Injectable({ providedIn: UserModule, }) export class UserService { }
src/app/user.service.ts
      
      import { Injectable } from '@angular/core';
import { UserModule } from './user.module';

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

上面的例子展示的就是在模組中提供服務的首選方式。之所以推薦該方式,是因為當沒有人注入它時,該服務就可以被搖樹優化掉。如果沒辦法指定哪個模組該提供這個服務,你也可以在那個模組中為該服務宣告一個提供者:

The example above shows the preferred way to provide a service in a module. This method is preferred because it enables tree-shaking of the service if nothing injects it. If it's not possible to specify in the service which module should provide it, you can also declare a provider for the service within the module:

import { NgModule } from '@angular/core'; import { UserService } from './user.service'; @NgModule({ providers: [UserService], }) export class UserModule { }
src/app/user.module.ts
      
      import { NgModule } from '@angular/core';

import { UserService } from './user.service';

@NgModule({
  providers: [UserService],
})
export class UserModule {
}
    

使用延遲載入模組限制提供者的作用域

Limiting provider scope by lazy loading modules

在 CLI 產生的基本應用中,模組是急性載入的,這意味著它們都是由本應用啟動的,Angular 會使用一個依賴注入體系來讓一切服務都在模組間有效。對於急性載入式應用,應用中的根注入器會讓所有服務提供者都對整個應用有效。

In the basic CLI-generated app, modules are eagerly loaded which means that they are all loaded when the app launches. Angular uses an injector system to make things available between modules. In an eagerly loaded app, the root application injector makes all of the providers in all of the modules available throughout the app.

當使用延遲載入時,這種行為需要進行改變。延遲載入就是只有當需要時才載入模組,比如路由中。它們沒辦法像急性載入模組那樣進行載入。這意味著,在它們的 providers 陣列中列出的服務都是不可用的,因為根注入器並不知道這些模組。

This behavior necessarily changes when you use lazy loading. Lazy loading is when you load modules only when you need them; for example, when routing. They aren’t loaded right away like with eagerly loaded modules. This means that any services listed in their provider arrays aren’t available because the root injector doesn’t know about these modules.

當 Angular 的路由器延遲載入一個模組時,它會建立一個新的注入器。這個注入器是應用的根注入器的一個子注入器。想象一棵注入器樹,它有唯一的根注入器,而每一個延遲載入模組都有一個自己的子注入器。路由器會把根注入器中的所有提供者新增到子注入器中。如果路由器在延遲載入時建立元件,Angular 會更傾向於使用從這些提供者中建立的服務實例,而不是來自應用的根注入器的服務實例。

When the Angular router lazy-loads a module, it creates a new injector. This injector is a child of the root application injector. Imagine a tree of injectors; there is a single root injector and then a child injector for each lazy loaded module. The router adds all of the providers from the root injector to the child injector. 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.

任何在延遲載入模組的上下文中建立的元件(比如路由導航),都會獲取該服務的區域性實例,而不是應用的根注入器中的實例。而外部模組中的元件,仍然會收到來自於應用的根注入器建立的實例。

Any component created within a lazy loaded module’s context, such as by router navigation, gets the local instance of the service, not the instance in the root application injector. Components in external modules continue to receive the instance created for the application root.

雖然你可以使用延遲載入模組來提供實例,但不是所有的服務都能延遲載入。比如,像路由之類別的模組只能在根模組中使用。路由器需要使用瀏覽器中的全域性物件 location 進行工作。

Though you can provide services by lazy loading modules, not all services can be lazy loaded. For instance, some modules only work in the root module, such as the Router. The Router works with the global location object in the browser.

從 Angular 9 開始,你可以在每個延遲載入模組中提供服務的新實例。下列程式碼把此功能新增到 UserService 中。

As of Angular version 9, you can provide a new instance of a service with each lazy loaded module. The following code adds this functionality to UserService.

import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'any', }) export class UserService { }
src/app/user.service.ts
      
      import { Injectable } from '@angular/core';

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

透過使用 providedIn: 'any',所有急性載入的模組都會共享同一個服務單例,不過,延遲載入模組各自有它們自己獨有的單例。如下所示:

With providedIn: 'any', all eagerly loaded modules share a singleton instance; however, lazy loaded modules each get their own unique instance, as shown in the following diagram.

any-provider-scope

使用元件限定服務提供者的作用域

Limiting provider scope with components

另一種限定提供者作用域的方式是把要限定的服務新增到元件的 providers 陣列中。元件中的提供者和 NgModule 中的提供者是彼此獨立的。 當你要急性載入一個自帶了全部所需服務的模組時,這種方式是有幫助的。 在元件中提供服務,會限定該服務只能在該元件及其子元件中有效,而同一模組中的其它元件不能訪問它。

Another way to limit provider scope is by adding the service you want to limit to the component’s providers array. Component providers and NgModule providers are independent of each other. This method is helpful when you want to eagerly load a module that needs a service all to itself. Providing a service in the component limits the service only to that component and its descendants. Other components in the same module can’t access it.

@Component({ /* . . . */ providers: [UserService] })
src/app/app.component.ts
      
      @Component({
/* . . . */
  providers: [UserService]
})
    

在模組中提供服務還是在元件中?

Providing services in modules vs. components

通常,要在根模組中提供整個應用都需要的服務,在延遲載入模組中提供限定範圍的服務。

Generally, provide services the whole app needs in the root module and scope services by providing them in lazy loaded modules.

路由器工作在根級,所以如果你把服務提供者放進元件(即使是 AppComponent)中,那些依賴於路由器的延遲載入模組,將無法看到它們。

The router works at the root level so if you put providers in a component, even AppComponent, lazy loaded modules, which rely on the router, can’t see them.

當你必須把一個服務實例的作用域限定到元件及其元件樹中時,可以使用元件註冊一個服務提供者。 比如,使用者編輯元件 UserEditorComponent,它需要一個快取 UserService 實例,那就應該把 UserService 註冊進 UserEditorComponent 中。 然後,每個 UserEditorComponent 的實例都會獲取它自己的快取服務實例。

Register a provider with a component when you must limit a service instance to a component and its component tree, that is, its child components. For example, a user editing component, UserEditorComponent, that needs a private copy of a caching UserService should register the UserService with the UserEditorComponent. Then each new instance of the UserEditorComponent gets its own cached service instance.


關於 NgModule 的更多知識

More on NgModules

你還可能對下列內容感興趣:

You may also be interested in: