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

服務與依賴注入簡介

Introduction to services and dependency injection

服務是一個廣義的概念,它包括應用所需的任何值、函式或特性。狹義的服務是一個明確定義了用途的類別。它應該做一些具體的事,並做好。

Service is a broad category encompassing any value, function, or feature that an app needs. A service is typically a class with a narrow, well-defined purpose. It should do something specific and do it well.

Angular 把元件和服務區分開,以提高模組性和複用性。 透過把元件中和檢視有關的功能與其它型別的處理分離開,你可以讓元件類別更加精簡、高效。

Angular distinguishes components from services to increase modularity and reusability. By separating a component's view-related functionality from other kinds of processing, you can make your component classes lean and efficient.

理想情況下,元件的工作只管使用者體驗,而不用顧及其它。 它應該提供用於資料繫結的屬性和方法,以便作為檢視(由範本渲染)和應用邏輯(通常包含一些模型的概念)的中介者。

Ideally, a component's job is to enable the user experience and nothing more. A component should present properties and methods for data binding, in order to mediate between the view (rendered by the template) and the application logic (which often includes some notion of a model).

元件應該把諸如從伺服器獲取資料、驗證使用者輸入或直接往控制檯中寫日誌等工作委託給各種服務。透過把各種處理任務定義到可注入的服務類別中,你可以讓它被任何元件使用。 透過在不同的環境中注入同一種服務的不同提供者,你還可以讓你的應用更具適應性。

A component can delegate certain tasks to services, such as fetching data from the server, validating user input, or logging directly to the console. By defining such processing tasks in an injectable service class, you make those tasks available to any component. You can also make your app more adaptable by injecting different providers of the same kind of service, as appropriate in different circumstances.

Angular 不會強迫你遵循這些原則。Angular 只會透過依賴注入來幫你更容易地將應用邏輯分解為服務,並讓這些服務可用於各個元件中。

Angular doesn't enforce these principles. Angular does help you follow these principles by making it easy to factor your application logic into services and make those services available to components through dependency injection.

服務範例

Service examples

下面是一個服務類別的範例,用於把日誌記錄到瀏覽器的控制檯:

Here's an example of a service class that logs to the browser console.

export class Logger { log(msg: any) { console.log(msg); } error(msg: any) { console.error(msg); } warn(msg: any) { console.warn(msg); } }
src/app/logger.service.ts (class)
      
      export class Logger {
  log(msg: any)   { console.log(msg); }
  error(msg: any) { console.error(msg); }
  warn(msg: any)  { console.warn(msg); }
}
    

服務也可以依賴其它服務。比如,這裡的 HeroService 就依賴於 Logger 服務,它還用 BackendService 來獲取英雄資料。BackendService 還可能再轉而依賴 HttpClient 服務來從伺服器非同步獲取英雄列表。

Services can depend on other services. For example, here's a HeroService that depends on the Logger service, and also uses BackendService to get heroes. That service in turn might depend on the HttpClient service to fetch heroes asynchronously from a server.

export class HeroService { private heroes: Hero[] = []; constructor( private backend: BackendService, private logger: Logger) { } getHeroes() { this.backend.getAll(Hero).then( (heroes: Hero[]) => { this.logger.log(`Fetched ${heroes.length} heroes.`); this.heroes.push(...heroes); // fill cache }); return this.heroes; } }
src/app/hero.service.ts (class)
      
      export class HeroService {
  private heroes: Hero[] = [];

  constructor(
    private backend: BackendService,
    private logger: Logger) { }

  getHeroes() {
    this.backend.getAll(Hero).then( (heroes: Hero[]) => {
      this.logger.log(`Fetched ${heroes.length} heroes.`);
      this.heroes.push(...heroes); // fill cache
    });
    return this.heroes;
  }
}
    

依賴注入(dependency injection)

Dependency injection (DI)

Service

DI 被融入 Angular 框架中,用於在任何地方給新建的元件提供服務或所需的其它東西。 元件是服務的消費者,也就是說,你可以把一個服務注入到元件中,讓元件類別得以訪問該服務類別。

DI is wired into the Angular framework and used everywhere to provide new components with the services or other things they need. Components consume services; that is, you can inject a service into a component, giving the component access to that service class.

在 Angular 中,要把一個類別定義為服務,就要用 @Injectable() 裝飾器來提供元資料,以便讓 Angular 可以把它作為依賴注入到元件中。 同樣,也要使用 @Injectable() 裝飾器來表明一個元件或其它類別(比如另一個服務、管道或 NgModule)擁有一個依賴。

To define a class as a service in Angular, use the @Injectable() decorator to provide the metadata that allows Angular to inject it into a component as a dependency. Similarly, use the @Injectable() decorator to indicate that a component or other class (such as another service, a pipe, or an NgModule) has a dependency.

  • 注入器是主要的機制。Angular 會在啟動過程中為你建立全應用級注入器以及所需的其它注入器。你不用自己建立注入器。

    The injector is the main mechanism. Angular creates an application-wide injector for you during the bootstrap process, and additional injectors as needed. You don't have to create injectors.

  • 該注入器會建立依賴、維護一個容器來管理這些依賴,並儘可能複用它們。

    An injector creates dependencies, and maintains a container of dependency instances that it reuses if possible.

  • 提供者是一個物件,用來告訴注入器應該如何獲取或建立依賴。

    A provider is an object that tells an injector how to obtain or create a dependency.

你的應用中所需的任何依賴,都必須使用該應用的注入器來註冊一個提供者,以便注入器可以使用這個提供者來建立新實例。 對於服務,該提供者通常就是服務類別本身。

For any dependency that you need in your app, you must register a provider with the app's injector, so that the injector can use the provider to create new instances. For a service, the provider is typically the service class itself.

依賴不一定是服務 —— 它還可能是函式或值。

A dependency doesn't have to be a service—it could be a function, for example, or a value.

當 Angular 建立元件類別的新實例時,它會透過檢視該元件類別的建構函式,來決定該元件依賴哪些服務或其它依賴項。 比如 HeroListComponent 的建構函式中需要 HeroService

When Angular creates a new instance of a component class, it determines which services or other dependencies that component needs by looking at the constructor parameter types. For example, the constructor of HeroListComponent needs HeroService.

constructor(private service: HeroService) { }
src/app/hero-list.component.ts (constructor)
      
      constructor(private service: HeroService) { }
    

當 Angular 發現某個元件依賴某個服務時,它會首先檢查是否該注入器中已經有了那個服務的任何現有實例。如果所請求的服務尚不存在,注入器就會使用以前註冊的服務提供者來製作一個,並把它加入注入器中,然後把該服務返回給 Angular。

When Angular discovers that a component depends on a service, it first checks if the injector has any existing instances of that service. If a requested service instance doesn't yet exist, the injector makes one using the registered provider, and adds it to the injector before returning the service to Angular.

當所有請求的服務已解析並返回時,Angular 可以用這些服務實例為引數,呼叫該元件的建構函式。

When all requested services have been resolved and returned, Angular can call the component's constructor with those services as arguments.

HeroService 的注入過程如下所示:

The process of HeroService injection looks something like this.

提供服務

Providing services

對於要用到的任何服務,你必須至少註冊一個提供者。服務可以在自己的元資料中把自己註冊為提供者,這樣可以讓自己隨處可用。或者,你也可以為特定的模組或元件註冊提供者。要註冊提供者,就要在服務的 @Injectable() 裝飾器中提供它的元資料,或者在 @NgModule()@Component() 的元資料中。

You must register at least one provider of any service you are going to use. The provider can be part of the service's own metadata, making that service available everywhere, or you can register providers with specific modules or components. You register providers in the metadata of the service (in the @Injectable() decorator), or in the @NgModule() or @Component() metadata

  • 預設情況下,Angular CLI 的 ng generate service命令會在 @Injectable() 裝飾器中提供元資料來把它註冊到根注入器中。本課程就用這種方法註冊了 HeroService 的提供者:

    By default, the Angular CLI command ng generate serviceregisters a provider with the root injector for your service by including provider metadata in the @Injectable() decorator. The tutorial uses this method to register the provider of HeroService class definition.

@Injectable({ providedIn: 'root', })
      
      @Injectable({
  providedIn: 'root',
})
    

當你在根一級提供服務時,Angular 會為 HeroService 建立一個單一的共享實例,並且把它注入到任何想要它的類別中。這種在 @Injectable 元資料中註冊提供者的方式還讓 Angular 能夠透過移除那些從未被用過的服務來優化大小。

When you provide the service at the root level, Angular creates a single, shared instance of HeroService and injects it into any class that asks for it. Registering the provider in the @Injectable() metadata also allows Angular to optimize an app by removing the service from the compiled app if it isn't used, a process known as tree-shaking.

  • 當你使用特定的 NgModule 註冊提供者時,該服務的同一個實例將會對該 NgModule 中的所有元件可用。要想在這一層註冊,請用 @NgModule() 裝飾器中的 providers 屬性:

    When you register a provider with a specific NgModule, the same instance of a service is available to all components in that NgModule. To register at this level, use the providers property of the @NgModule() decorator.

@NgModule({ providers: [ BackendService, Logger ], ... })
      
      @NgModule({
  providers: [
   BackendService,
   Logger
 ],
 ...
})
    
  • 當你在元件級註冊提供者時,你會為該元件的每一個新實例提供該服務的一個新實例。 要在元件級註冊,就要在 @Component() 元資料的 providers 屬性中註冊服務提供者。

    When you register a provider at the component level, you get a new instance of the service with each new instance of that component. At the component level, register a service provider in the providers property of the @Component() metadata.

@Component({ selector: 'app-hero-list', templateUrl: './hero-list.component.html', providers: [ HeroService ] })
src/app/hero-list.component.ts (component providers)
      
      @Component({
  selector:    'app-hero-list',
  templateUrl: './hero-list.component.html',
  providers:  [ HeroService ]
})
    

要了解更多細節,請參閱依賴注入一節。

For more detailed information, see the Dependency Injection section.