创建和注册可注入的服务
DI 框架让你能从一个可注入的服务类(独立文件)中为组件提供数据。为了演示,我们还会创建一个用来提供英雄列表的、可注入的服务类,并把它注册为该服务的提供商。
创建可注入的服务类
Angular CLI 可以用下列命令在 src/app/heroes
目录下生成一个新的 HeroService
类。
ng generate service heroes/hero
上列命令会创建 HeroService
的骨架。
import { Injectable } from '@angular/core';
@Injectable({ //依赖注入
providedIn: 'root',
})
export class HeroService {
constructor() { }
}
@Injectable()
是每个 Angular 服务定义中的基本要素。该类的其余部分导出了一个 getHeroes
方法,它会返回像以前一样的模拟数据。
import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';
@Injectable({
// we declare that this service should be created
// by the root application injector.
providedIn: 'root',
})
export class HeroService {
getHeroes() { return HEROES; }
}
用服务提供商配置注入器
我们创建的类提供了一个服务。@Injectable()
装饰器把它标记为可供注入的服务,不过在你使用该服务的 provider 提供商配置好 Angular 的依赖注入器之前,Angular 实际上无法将其注入到任何位置。
该注入器负责创建服务实例,并把它们注入到像 HeroListComponent
这样的类中。 你很少需要自己创建 Angular 的注入器。Angular 会在执行应用时为你创建注入器,第一个注入器是根注入器,创建于启动过程中。
提供商会告诉注入器如何创建该服务。 要想让注入器能够创建服务(或提供其它类型的依赖),你必须使用某个提供商配置好注入器。
提供商可以是服务类本身,因此注入器可以使用 new
来创建实例。 你还可以定义多个类,以不同的方式提供同一个服务,并使用不同的提供商来配置不同的注入器。
你可以在三种位置之一设置元数据,以便在应用的不同层级使用提供商来配置注入器:
-
在服务本身的
@Injectable()
装饰器中 //提供商是在injectable里面定义的 -
在 NgModule 的
@NgModule()
装饰器中。//在ngmodule中定义则是根组件提供商 -
在组件的
@Component()
装饰器中。 //组件提供商
@Injectable()
装饰器具有一个名叫 providedIn
的元数据选项,在那里你可以指定把被装饰类的提供商放到 root
注入器中,或某个特定 NgModule 的注入器中。
@NgModule()
和 @Component()
装饰器都有用一个 providers
元数据选项,在那里你可以配置 NgModule 级或组件级的注入器。
注入服务
HeroListComponent
要想从 HeroService
中获取英雄列表,就得要求注入 HeroService
,而不是自己使用 new
来创建自己的 HeroService
实例。
你可以通过制定带有依赖类型的构造函数参数来要求 Angular 在组件的构造函数中注入依赖项。下面的代码是 HeroListComponent
的构造函数,它要求注入 HeroService
。
constructor(heroService: HeroService) //通过构造函数来注入服务
当然,HeroListComponent
还应该使用注入的这个 HeroService
做一些事情。 这里是修改过的组件,它转而使用注入的服务。与前一版本并列显示,以便比较。
import { Component } from '@angular/core';
import { Hero } from './hero';
import { HeroService } from './hero.service';
@Component({
selector: 'app-hero-list',
template: `
<div *ngFor="let hero of heroes">
{{hero.id}} - {{hero.name}}
</div>
`
})
export class HeroListComponent {
heroes: Hero[];
constructor(heroService: HeroService) { //构造函数注入服务
this.heroes = heroService.getHeroes(); //从服务中获取数据
}
}
注入器树与服务实例
在某个注入器的范围内,服务是单例的。也就是说,在指定的注入器中最多只有某个服务的最多一个实例。
应用只有一个根注入器。在 root
或 AppModule
级提供 UserService
意味着它注册到了根注入器上。 在整个应用中只有一个 UserService
实例,每个要求注入 UserService
的类都会得到这一个服务实例,除非你在子注入器中配置了另一个提供商。
Angular DI 具有分层注入体系,这意味着下级注入器也可以创建它们自己的服务实例。 Angular 会有规律的创建下级注入器。每当 Angular 创建一个在 @Component()
中指定了 providers
的组件实例时,它也会为该实例创建一个新的子注入器。 类似的,当在运行期间加载一个新的 NgModule 时,Angular 也可以为它创建一个拥有自己的提供商的注入器。
子模块和组件注入器彼此独立,并且会为所提供的服务分别创建自己的实例。当 Angular 销毁 NgModule 或组件实例时,也会销毁这些注入器以及注入器中的那些服务实例。
借助注入器继承机制,你仍然可以把全应用级的服务注入到这些组件中。 组件的注入器是其父组件注入器的子节点,它会继承所有的祖先注入器,其终点则是应用的根注入器。 Angular 可以注入该继承谱系中任何一个注入器提供的服务。
比如,Angular 既可以把 HeroComponent
中提供的 HeroService
注入到 HeroListComponent
,也可以注入 AppModule
中提供的 UserService
。
那些需要其它服务的服务
服务还可以具有自己的依赖。HeroService
非常简单,没有自己的依赖。不过,如果你希望通过日志服务来报告这些活动,那么就可以使用同样的构造函数注入模式,添加一个构造函数来接收一个 Logger
参数。
这是修改后的 HeroService
,它注入了 Logger
,我们把它和前一个版本的服务放在一起进行对比。
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;
}
}
该构造函数请求注入一个 Logger
的实例,并把它保存在一个名叫 logger
的私有字段中。 当要求获取英雄列表时,getHeroes()
方法就会记录一条消息。
注意,虽然 Logger
服务没有自己的依赖项,但是它同样带有 @Injectable()
装饰器。实际上,@Injectable()
对所有服务都是必须的。
当 Angular 创建一个构造函数中有参数的类时,它会查找有关这些参数的类型,和供注入使用的元数据,以便找到正确的服务。 如果 Angular 无法找到参数信息,它就会抛出一个错误。 只有当类具有某种装饰器时,Angular 才能找到参数信息。@Injectable()
装饰器是所有服务类的标准装饰器。
依赖注入令牌
当使用提供商配置注入器时,就会把提供商和一个 DI 令牌关联起来。 注入器维护一个内部令牌-提供商的映射表,当请求一个依赖项时就会引用它。令牌就是这个映射表的键。
在简单的例子中,依赖项的值是一个实例,而类的类型则充当键来查阅它。 通过把 HeroService
类型作为令牌,你可以直接从注入器中获得一个 HeroService
实例。
heroService: HeroService;
可选依赖
HeroService
需要一个记录器,但是如果找不到它会怎么样?
当组件或服务声明某个依赖项时,该类的构造函数会以参数的形式接收那个依赖项。 通过给这个参数加上 @Optional()
注解,你可以告诉 Angular,该依赖是可选的。
import { Optional } from '@angular/core';
constructor(@Optional() private logger: Logger) { //可选的依赖,当有可选的依赖的时候要注意处理当为空的时候的情况
if (this.logger) {
this.logger.log(some_message);
}
}