# 创建可注入服务

服务是一个很宽泛的类别,它包含应用程序需要的任何值、功能或特性。服务通常是具有狭窄、明确定义的目标的类。组件是一种可以使用 DI 的类。

Angular 将组件与服务区分开来,是为了提高模块化程度和可复用性。通过将组件中与视图相关的特性与其他类型的处理分离开,可以让你的组件类更加精简高效。

理想情况下,组件的工作就是提供用户体验,并无其它职责。组件应该表达用于数据绑定的属性和方法,以在视图(由模板渲染)和应用逻辑(通常包括某个模型的一些概念)之间进行居中协调。

组件可以将某些任务委托给服务,例如从服务器获取数据、验证用户输入或直接把日志记录到控制台。通过在可注入服务类中定义这样的处理任务,你可以让这些任务可用于任何组件。你还可以通过在不同的情况下注入同一个服务的不同提供者来让你的应用程序适应更多场景。

Angular 不会强制执行这些原则。 Angular 只是让你可以轻松地将应用逻辑分解为服务,并通过 DI 让这些服务可用在组件中,从而帮助你遵循这些原则。

# 服务范例

下面是一个服务类的范例,用于把日志记录到浏览器的控制台:。

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服务来从服务器异步获取英雄列表。

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;
  }
}

# 创建可注入的服务

Angular CLI 提供了一个命令来创建新服务。在以下示例中,你会为应用程序添加新服务,该应用是之前使用 ng new命令创建的。

要在 src/app/heroes文件夹中生成新的 HeroService类,请按照以下步骤操作:

* 运行此 Angular CLI 命令:

ng generate service heroes/hero

下列命令会创建默认的 HeroService

src/app/heroes/hero.service.ts (CLI-generated)

import { Injectable } from '@angular/core';

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

@Injectable()装饰器指出 Angular 可以在 DI 体系中使用此类。元数据 providedIn: 'root'表示 HeroService在整个应用程序中都是可见的。

* 添加一个 getHeroes()方法,该方法会返回来自 mock.heroes.ts的英雄列表,以获取英雄的模拟数据:

src/app/heroes/hero.service.ts

import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';

@Injectable({
  // declares that this service should be created
  // by the root application injector.
  providedIn: 'root',
})
export class HeroService {
  getHeroes() { return HEROES; }
}

为了清晰和可维护性,建议你在单独的文件中定义组件和服务。

# 注入服务

要将服务作为依赖项注入到组件中,你可以使用组件的 constructor(),并为构造函数添加一个该依赖类型的参数。以下示例会在 HeroListComponent构造函数中指定 HeroService。这里 heroService的类型是 HeroService。Angular 会将 HeroService识别为依赖项,因为该类以前用 @Injectable装饰器标记过。

src/app/heroes/hero-list.component (constructor signature)

constructor(heroService: HeroService)

# 在其他服务中注入服务

当某个服务依赖于另一个服务时,请遵循与注入组件相同的模式。在这里,HeroService要依靠 Logger服务来报告其活动。

首先,导入 Logger服务。接下来,通过指定 private logger: Logger,在 HeroServiceconstructor()中注入 Logger服务。

在这里,constructor()指定了 Logger的类型,并把 Logger的实例存储在名叫 logger的私有字段中。

下列代码具有 Logger服务和两个版本的 HeroServiceHeroService的第一个版本不依赖于 Logger服务。修改后的第二个版本依赖于 Logger服务。

src/app/heroes/hero.service (v2)

import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';
import { Logger } from '../logger.service';

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

  constructor(private logger: Logger) {  }

  getHeroes() {
    this.logger.log('Getting heroes ...');
    return HEROES;
  }
}

src/app/heroes/hero.service (v1)

import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';

@Injectable({
  providedIn: 'root',
})
export class HeroService {
  getHeroes() { return HEROES; }
}

src/app/logger.service

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class Logger {
  logs: string[] = []; // capture logs for testing

  log(message: string) {
    this.logs.push(message);
    console.log(message);
  }
}

在这个例子中,getHeroes()方法在获取英雄时就会借助 Logger记录一条消息。

# 下一步呢?

如何在 DI 中配置依赖项

如何使用 InjectionTokens 提供和注入服务/类之外的值

依赖注入实战

Last Updated: 5/5/2023, 9:06:42 AM