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

依賴提供者

Dependency providers

透過配置提供者,你可以把服務提供給那些需要它們的應用部件。

By configuring providers, you can make services available to the parts of your application that need them.

依賴提供者會使用 DI 令牌來配置注入器,注入器會用它來提供這個依賴值的具體的、執行時版本。

A dependency provider configures an injector with a DI token, which that injector uses to provide the runtime version of a dependency value.

指定提供者令牌

Specifying a provider token

如果你把服務類別指定為提供者令牌,那麼注入器的預設行為是用 new 來實例化那個類別。

If you specify the service class as the provider token, the default behavior is for the injector to instantiate that class with new.

在下面這個例子中,Logger 類別提供了 Logger 的實例。

In the following example, the Logger class provides a Logger instance.

      
      providers: [Logger]
    

不過,你也可以用一個替代提供者來配置注入器,這樣就可以指定另一些同樣能提供日誌功能的物件。

You can, however, configure an injector with an alternative provider in order to deliver some other object that provides the needed logging functionality.

你可以使用服務類別來配置注入器,也可以提供一個替代類別、一個物件或一個工廠函式。

You can configure an injector with a service class, you can provide a substitute class, an object, or a factory function.

依賴注入令牌

Dependency injection tokens

當使用提供者配置注入器時,會將該提供者與依賴項注入令牌(或叫 DI 令牌)關聯起來。注入器允許 Angular 建立任何內部依賴項的對映。DI 令牌會充當該對映的鍵名。

When you configure an injector with a provider, you are associating that provider with a dependency injection token, or DI token. The injector allows Angular to create a map of any internal dependencies. The DI token acts as a key to that map.

依賴項值是一個實例,而這個類別的型別用作查詢鍵。在這裡,注入器使用 HeroService 型別作為令牌來查詢 heroService

The dependency value is an instance, and the class type serves as a lookup key. Here, the injector uses the HeroService type as the token for looking up heroService.

src/app/injector.component.ts
      
      heroService: HeroService;
    

當你使用 HeroService 類別的型別來定義建構函式引數時,Angular 會注入與這個 HeroService 類別令牌相關聯的服務:

When you define a constructor parameter with the HeroService class type, Angular knows to inject the service associated with that HeroService class token:

src/app/heroes/hero-list.component.ts
      
      constructor(heroService: HeroService)
    

儘管許多依賴項的值是透過類別提供的,但擴充套件的 provide 物件使你可以將不同種類的提供者與 DI 令牌相關聯。

Though classes provide many dependency values, the expanded provide object lets you associate different kinds of providers with a DI token.

定義提供者

Defining providers

類別提供者的語法實際上是一種簡寫形式,它會擴充套件成一個由 Provider 介面定義的提供者配置物件。 下面的程式碼片段展示了 providers 中給出的類別會如何擴充套件成完整的提供者配置物件。

The class provider syntax is a shorthand expression that expands into a provider configuration, defined by the Provider interface. The following example is the class provider syntax for providing a Logger class in the providers array.

      
      providers: [Logger]
    

Angular 把這個 providers 值擴充套件為一個完整的提供者物件,如下所示。

Angular expands the providers value into a full provider object as follows.

      
      [{ provide: Logger, useClass: Logger }]
    

擴充套件的提供者配置是一個具有兩個屬性的物件字面量:

The expanded provider configuration is an object literal with two properties:

  • provide 屬性存有令牌,它作為一個 key,在定位依賴值和配置注入器時使用。

    The provide property holds the token that serves as the key for both locating a dependency value and configuring the injector.

  • 第二個屬性是一個提供者定義物件,它告訴注入器要如何建立依賴值。 提供者定義物件中的 key 可以是 useClass —— 就像這個例子中一樣。 也可以是 useExistinguseValueuseFactory。 每一個 key 都用於提供一種不同型別的依賴,我們稍後會討論。

    The second property is a provider definition object, which tells the injector how to create the dependency value. The provider-definition key can be useClass, as in the example. It can also be useExisting, useValue, or useFactory. Each of these keys provides a different type of dependency, as discussed below.

指定替代性的類別提供者

Specifying an alternative class provider

不同的類別可以提供相同的服務。例如,以下程式碼告訴注入器,當元件使用 Logger 令牌請求一個 logger 時,給它返回一個 BetterLogger

Different classes can provide the same service. For example, the following code tells the injector to return a BetterLogger instance when the component asks for a logger using the Logger token.

      
      [{ provide: Logger, useClass: BetterLogger }]
    

配置帶依賴的類別提供者

Configuring class providers with dependencies

如果替代類別提供者有自己的依賴,那就在父模組或元件的元資料屬性 providers 中指定那些依賴。

If the alternative class providers have their own dependencies, specify both providers in the providers metadata property of the parent module or component.

      
      [ UserService,
  { provide: Logger, useClass: EvenBetterLogger }]
    

在這個例子中,EvenBetterLogger 會在日誌資訊裡顯示使用者名稱。 這個 logger 要從注入的 UserService 實例中來獲取該使用者。

In this example, EvenBetterLogger displays the user name in the log message. This logger gets the user from an injected UserService instance.

      
      @Injectable()
export class EvenBetterLogger extends Logger {
  constructor(private userService: UserService) { super(); }

  log(message: string) {
    const name = this.userService.user.name;
    super.log(`Message to ${name}: ${message}`);
  }
}
    

注入器需要提供這個新的日誌服務以及該服務所依賴的 UserService 物件。

The injector needs providers for both this new logging service and its dependent UserService.

別名類別提供者

Aliasing class providers

要為類別提供者設定別名,請在 providers 陣列中使用 useExisting 屬性指定別名和類別提供者。

To alias a class provider, specify the alias and the class provider in the providers array with the useExisting property.

在下面的例子中,當元件請求新的或舊的記錄器時,注入器都會注入一個 NewLogger 的實例。 透過這種方式,OldLogger 就成了 NewLogger 的別名。

In the following example, the injector injects the singleton instance of NewLogger when the component asks for either the new or the old logger. In this way, OldLogger is an alias for NewLogger.

      
      [ NewLogger,
  // Alias OldLogger w/ reference to NewLogger
  { provide: OldLogger, useExisting: NewLogger}]
    

請確保你沒有使用 useClass 來把 OldLogger 設為 NewLogger 的別名,因為如果這樣做它就會建立兩個不同的 NewLogger 實例。

Be sure you don't alias OldLogger to NewLogger with useClass, as this creates two different NewLogger instances.

為類別介面指定別名

Aliasing a class interface

通常,編寫同一個父元件別名提供者的變體時會使用forwardRef,如下所示。

Generally, writing variations of the same parent alias provider uses forwardRef as follows.

dependency-injection-in-action/src/app/parent-finder.component.ts
      
      providers: [{ provide: Parent, useExisting: forwardRef(() => AlexComponent) }],
    

為簡化你的程式碼,可以使用輔助函式 provideParent() 來把這個邏輯提取到一個輔助函式中。

To streamline your code, you can extract that logic into a helper function using the provideParent() helper function.

dependency-injection-in-action/src/app/parent-finder.component.ts
      
      // Helper method to provide the current component instance in the name of a `parentType`.
export function provideParent
  (component: any) {
    return { provide: Parent, useExisting: forwardRef(() => component) };
  }
    

現在,你可以為元件新增一個更容易閱讀和理解的父提供者。

Now you can add a parent provider to your components that's easier to read and understand.

dependency-injection-in-action/src/app/parent-finder.component.ts
      
      providers:  [ provideParent(AliceComponent) ]
    

為多個類別介面指定別名

Aliasing multiple class interfaces

要為多個父型別指定別名(每個型別都有自己的類別介面令牌),請配置 provideParent() 以接受更多的引數。

To alias multiple parent types, each with its own class interface token, configure provideParent() to accept more arguments.

這是一個修訂版本,預設值為 parent 但同時也接受另一個父類別介面作為可選的第二引數。

Here's a revised version that defaults to parent but also accepts an optional second parameter for a different parent class interface.

dependency-injection-in-action/src/app/parent-finder.component.ts
      
      // Helper method to provide the current component instance in the name of a `parentType`.
// The `parentType` defaults to `Parent` when omitting the second parameter.
export function provideParent
  (component: any, parentType?: any) {
    return { provide: parentType || Parent, useExisting: forwardRef(() => component) };
  }
    

接下來,要使用 provideParent(),請傳入第二引數,這裡是 DifferentParent

Next, to use provideParent() with a different parent type, provide a second argument, here DifferentParent.

dependency-injection-in-action/src/app/parent-finder.component.ts
      
      providers:  [ provideParent(BethComponent, DifferentParent) ]
    

注入一個物件

Injecting an object

要注入一個物件,可以用 useValue 選項來配置注入器。 下面的提供者定義物件使用 useValue 作為 key 來把該變數與 Logger 令牌關聯起來。

To inject an object, configure the injector with the useValue option. The following provider object uses the useValue key to associate the variable with the Logger token.

      
      [{ provide: Logger, useValue: SilentLogger }]
    

在這個例子中,SilentLogger 是一個充當記錄器角色的物件。

In this example, SilentLogger is an object that fulfills the logger role.

      
      // An object in the shape of the logger service
function silentLoggerFn() {}

export const SilentLogger = {
  logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'],
  log: silentLoggerFn
};
    

注入一個配置物件

Injecting a configuration object

常用的物件字面量是配置物件。下列配置物件包括應用的標題和 Web API 的端點地址。

A common use case for object literals is a configuration object. The following configuration object includes the title of the application and the address of a web API endpoint.

src/app/app.config.ts (excerpt)
      
      export const HERO_DI_CONFIG: AppConfig = {
  apiEndpoint: 'api.heroes.com',
  title: 'Dependency Injection'
};
    

要提供並注入配置物件,請在 @NgModule()providers 陣列中指定該物件。

To provide and inject the configuration object, specify the object in the @NgModule() providers array.

src/app/app.module.ts (providers)
      
      providers: [
  UserService,
  { provide: APP_CONFIG, useValue: HERO_DI_CONFIG }
],
    

使用 InjectionToken 物件

Using an InjectionToken object

你可以定義和使用一個 InjectionToken 物件來為非類別的依賴選擇一個提供者令牌。下列例子定義了一個型別為 InjectionTokenAPP_CONFIG

You can define and use an InjectionToken object for choosing a provider token for non-class dependencies. The following example defines a token, APP_CONFIG of the type InjectionToken.

src/app/app.config.ts
      
      import { InjectionToken } from '@angular/core';

export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');
    

可選的引數 <AppConfig> 和令牌描述 app.config 指明瞭此令牌的用途。

The optional type parameter, <AppConfig>, and the token description, app.config, specify the token's purpose.

接著,用 APP_CONFIG 這個 InjectionToken 物件在元件中註冊依賴提供者。

Next, register the dependency provider in the component using the InjectionToken object of APP_CONFIG.

src/app/providers.component.ts
      
      providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]
    

現在,藉助引數裝飾器 @Inject(),你可以把這個配置物件注入到建構函式中。

Now you can inject the configuration object into the constructor with @Inject() parameter decorator.

src/app/app.component.ts
      
      constructor(@Inject(APP_CONFIG) config: AppConfig) {
  this.title = config.title;
}
    

介面和依賴注入

Interfaces and dependency injection

雖然 TypeScript 的 AppConfig 介面可以在類別中提供型別支援,但它在依賴注入時卻沒有任何作用。在 TypeScript 中,介面是一項設計期工件,它沒有可供 DI 框架使用的執行時表示形式或令牌。

Though the TypeScript AppConfig interface supports typing within the class, the AppConfig interface plays no role in dependency injection. In TypeScript, an interface is a design-time artifact, and doesn't have a runtime representation, or token, that the DI framework can use.

當轉譯器把 TypeScript 轉換成 JavaScript 時,介面就會消失,因為 JavaScript 沒有介面。

When the transpiler changes TypeScript to JavaScript, the interface disappears because JavaScript doesn't have interfaces.

由於 Angular 在執行期沒有介面,所以該介面不能作為令牌,也不能注入它。

Since there is no interface for Angular to find at runtime, the interface cannot be a token, nor can you inject it.

      
      // Can't use interface as provider token
[{ provide: AppConfig, useValue: HERO_DI_CONFIG })]
    
      
      // Can't inject using the interface as the parameter type
constructor(private config: AppConfig){ }
    

使用工廠提供者

Using factory providers

要想根據執行前尚不可用的資訊建立可變的依賴值,可以使用工廠提供者。

To create a changeable, dependent value based on information unavailable before run time, you can use a factory provider.

在下面的例子中,只有授權使用者才能看到 HeroService 中的祕密英雄。授權可能在單個應用會話期間發生變化,比如改用其他使用者登入。

In the following example, only authorized users should see secret heroes in the HeroService. Authorization can change during the course of a single application session, as when a different user logs in .

要想在 UserServiceHeroService 中儲存敏感資訊,就要給 HeroService 的建構函式傳一個邏輯標誌來控制祕密英雄的顯示。

To keep security-sensitive information in UserService and out of HeroService, give the HeroService constructor a boolean flag to control display of secret heroes.

src/app/heroes/hero.service.ts (excerpt)
      
      constructor(
  private logger: Logger,
  private isAuthorized: boolean) { }

getHeroes() {
  const auth = this.isAuthorized ? 'authorized ' : 'unauthorized';
  this.logger.log(`Getting heroes for ${auth} user.`);
  return HEROES.filter(hero => this.isAuthorized || !hero.isSecret);
}
    

要實現 isAuthorized 標誌,可以用工廠提供者來為 HeroService 建立一個新的 logger 實例。

To implement the isAuthorized flag, use a factory provider to create a new logger instance for HeroService.

src/app/heroes/hero.service.provider.ts (excerpt)
      
      const heroServiceFactory = (logger: Logger, userService: UserService) => {
  return new HeroService(logger, userService.user.isAuthorized);
};
    

這個工廠函式可以訪問 UserService。你可以同時把 LoggerUserService 注入到工廠提供者中,這樣注入器就可以把它們傳給工廠函數了。

The factory function has access to UserService. You inject both Logger and UserService into the factory provider so the injector can pass them along to the factory function.

src/app/heroes/hero.service.provider.ts (excerpt)
      
      export let heroServiceProvider =
  { provide: HeroService,
    useFactory: heroServiceFactory,
    deps: [Logger, UserService]
  };
    
  • useFactory 欄位指定該提供者是一個工廠函式,其實現程式碼是 heroServiceFactory

    The useFactory field specifies that the provider is a factory function whose implementation is heroServiceFactory.

  • deps 屬性是一個提供者令牌陣列。 LoggerUserService 類別都是自己類別提供者的令牌。該注入器解析了這些令牌,並把相應的服務注入到 heroServiceFactory 工廠函式的引數中。

    The deps property is an array of provider tokens. The Logger and UserService classes serve as tokens for their own class providers. The injector resolves these tokens and injects the corresponding services into the matching heroServiceFactory factory function parameters.

透過把工廠提供者匯出為變數 heroServiceProvider,就能讓工廠提供者變得可複用。

Capturing the factory provider in the exported variable, heroServiceProvider, makes the factory provider reusable.

下面這兩個並排的例子展示了在 providers 陣列中,如何用 heroServiceProvider 替換 HeroService

The following side-by-side example shows how heroServiceProvider replaces HeroService in the providers array.

      
      import { Component } from '@angular/core';
import { heroServiceProvider } from './hero.service.provider';

@Component({
  selector: 'app-heroes',
  providers: [ heroServiceProvider ],
  template: `
    <h2>Heroes</h2>
    <app-hero-list></app-hero-list>
  `
})
export class HeroesComponent { }