概念
维基百科:
在软件工程中,依赖注入是种实现控制反转用于解决依赖性设计模式。一个依赖关系指的是可被利用的一种对象(即服务提供端) 。依赖注入是将所依赖的传递给将使用的从属对象(即客户端)。该服务是将会变成客户端的状态的一部分。 传递服务给客户端,而非允许客户端来建立或寻找服务,是本设计模式的基本要求。
一般情况下,如果服务 A 需要服务 B,那就意味着服务 A 要在内部创建服务 B 的实例,也就说服务 A 依赖于服务 B:
Angular 利用依赖注入机制改变了这一点,在该机制下,如果服务 A 中需要服务 B,即服务 A 依赖于服务 B,那么我们期望服务 B 能被自动注入到服务 A 中,如下图所示:
在 Angular 中,依赖注入包括以下三个部分:
提供者负责把一个令牌(可能是字符串也可能是类)映射到一个依赖的列表。它告诉 Angular 该如何根据指定的令牌创建对象。
注入器负责持有一组绑定;当外界要求创建对象时,解析这些依赖并注入它们。
依赖就是将被用于注入的对象。
三者的关系图如下:
服务
为什么使用服务
组件不应该直接获取或保存数据,它们不应该了解是否在展示假数据。 它们应该聚焦于展示数据,而把数据访问的职责委托给某个服务。
创建 Angular 组件
@Component({
selector: 'app-root',
template: `
<h1>{{title}}</h1>
`
})
export class AppComponent {
title: string = 'App Works';
}
创建 Angular 服务
export class DataService {
getData() {
return ['Angular', 'React', 'Vue'];
}
}
组件中注入服务
按照官方文档的示例,我们创建一个HeroComponent
,它用来显示英雄的信息,具体实现如下:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-hero',
template: `
<ul>
<li *ngFor="let hero of heros">
ID: {{hero.id}} - Name: {{hero.name}}
</li>
</ul>
`
})
export class HeroComponent implements OnInit {
heros: Array<{ id: number; name: string }>;
ngOnInit() {
this.heros = [
{ id: 11, name: 'Mr. Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Celeritas' },
{ id: 15, name: 'Magneta' }
];
}
}
在 HeroComponent
组件中,我们在 ngOnInit
钩子中进行数据初始化,然后利用 ngFor
指令来显示英雄列表的信息。创建完 HeroComponent
组件,我们要来验证一下该组件的功能。首先在 AppModule
中导入 HeroComponent
组件,具体如下:
import { HeroComponent } from './hero/hero.component';
@NgModule({
declarations: [
AppComponent,
HeroComponent
],
...
})
export class AppModule { }
然后更新一下 AppComponent 组件,具体如下:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<app-hero></app-hero>
`
})
export class AppComponent {}
访问 http://localhost:4200/ 页面,将看到:
ID: 11 - Name: Mr. Nice
ID: 12 - Name: Narco
ID: 13 - Name: Bombasto
ID: 14 - Name: Celeritas
ID: 15 - Name: Magneta
在目前的 HeroComponent
组件,我们的英雄列表信息是固定的,在实际的开发场景中,一般需要从远程服务器获取相应的信息。但我们暂不考虑这个问题,假设另外一个组件也需要利用同样的英雄列表信息,我们就需要创建一个 HeroService
服务,从而实现数据共享。具体如下:
export class HeroService {
heros: Array<{ id: number; name: string }> = [
{ id: 11, name: 'Mr. Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Celeritas' },
{ id: 15, name: 'Magneta' }
];
getHeros() {
return this.heros;
}
}
在 HeroService
服务中,我们定义了一个 heros
属性和一个 getHeros()
方法:
heros
- 用于保存英雄的列表信息
getHeros()
- 用于获取英雄的列表信息
组件中使用 HeroService
导入 HeroService 服务
import { HeroService } from '../hero.service';
声明 HeroService 服务
@Component({
selector: 'app-hero',
...
providers: [HeroService]
})
注入 HeroService 服务
export class HeroComponent implements OnInit {
constructor(private heroService: HeroService) { }
}
完整代码如下:
import { Component, OnInit } from '@angular/core';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-hero',
template: `
<ul>
<li *ngFor="let hero of heros">
ID: {{hero.id}} - Name: {{hero.name}}
</li>
</ul>
`,
providers: [HeroService]
})
export class HeroComponent implements OnInit {
constructor(private heroService: HeroService) { }
heros: Array<{ id: number; name: string }>;
ngOnInit() {
this.heros = this.heroService.getHeros();
}
}
Provider
作用
注入器,Provider和对象间的关系
在 Angular 中我们通过 Provider 来描述与 Token 相关联的依赖对象的创建方式。
在 Angular 中依赖对象的创建方式分为以下四种:
useClass
useValue
useExisting
useFactory
使用
1.创建 Token
2.根据实际需求选择依赖对象的创建方式,如 useClass 、useValue、useExisting、useFactory
3.在 NgModule 或 Component 中注册 providers
4.使用构造注入的方式,注入与 Token 关联的依赖对象
分类
在 Angular 中 Provider 主要分为:
ClassProvider
ValueProvider
ExistingProvider
FactoryProvider
ClassProvider
不同的类都可用于提供相同的服务。
比如,下面的代码告诉注入器,当组件使用 FoodService 令牌请求日志对象时,给它返回一个 FoodService 实例。
@Component({
selector: 'drink-viewer',
providers: [
{ provide: FoodService, useClass: FoodService }
],
})
ValueProvider
有时候,提供一个现成的对象会比要求注入器从类去创建更简单一些。 如果要注入一个你已经创建过的对象,请使用 useValue 选项来配置该注入器。
@NgModule({
declarations: [
AppComponent,
],
providers: [
{ provide: 'api', useValue: '/api/pizzas' }
]
})
export class AppModule {}
ExistingProvider
@Component({
selector: 'drink-viewer',
providers: [
FoodService,
{ provide: DrinkService, useExisting: FoodService }
]
})
FactoryProvider
有时候你需要动态创建依赖值,创建时需要的信息你要等运行期间才能拿到。 比如,你可能需要某个在浏览器会话过程中会被反复修改的信息,而且这个可注入服务还不能独立访问这个信息的源头。
这种情况下,可以使用工厂提供商。
export function SideFactory(http) {
return new FoodService(http, '/api/sides');
}
@Component({
selector: 'side-viewer',
providers: [
{
provide: FoodService,
useFactory: SideFactory,
deps: [Http]
}
],
})
在 ValueProvider 的中,使用字符串作为 token,在大多数情况下,是不会存在问题的。
{ provide: 'api', useValue: '/api/pizzas' }
但假设某一天我们引入了一个第三方库,该库内部也是使用 ‘api’ 作为 token,这时候就会导致系统出现异常。
为了解决 token 冲突问题,Angular 引入了 InjectionToken
来避免出现 token 冲突。对于上面的示例,我们可以使用 InjectionToken
来创建一个唯一的 token:
export const API_TOKEN = new InjectionToken<string>('api');
使用的时候也很简单,只需要导入 API_TOKEN,然后更新 Provider 对象的 provide 属性,具体如下:
providers: [
{ provide: API_TOKEN, useValue: '/api/pizzas' }
]
Injectable
我们创建的类提供了一个服务。Injectable装饰器把它标记为可供注入的服务。
该注入器负责创建服务实例,并把它们注入到类中。Angular 会在执行应用时为你创建注入器,第一个注入器是根注入器,创建于启动过程中。
提供商会告诉注入器如何创建该服务。 要想让注入器能够创建服务(或提供其它类型的依赖),你必须使用某个Provider配置好注入器。
注入器是可继承的,这意味着如果指定的注入器无法解析某个依赖,它就会请求父注入器来解析它。
如果所创建的服务不依赖于其他对象,是可以不用使用 Injectable 类装饰器。但当该服务需要在构造函数中注入依赖对象,就需要使用 Injectable 装饰器。
用法
继续用上文官方示例的HeroComponent
组件
@Component({
selector: 'app-hero',
template: `
<ul>
<li *ngFor="let hero of heros">
ID: {{hero.id}} - Name: {{hero.name}}
</li>
</ul>
`
})
export class HeroComponent implements OnInit {
heros: Array<{ id: number; name: string }>;
constructor(private heroService: HeroService,
private loggerService: LoggerService) { }
ngOnInit() {
this.loggerService.log('Fetching heros...');
this.heros = this.heroService.getHeros();
}
}
然后是HeroService
import { LoggerService } from './logger.service';
export class HeroService {
constructor(private loggerService: LoggerService) { }
heros: Array<{ id: number; name: string }> = [
{ id: 11, name: 'Mr. Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Celeritas' },
{ id: 15, name: 'Magneta' }
];
getHeros() {
this.loggerService.log('Fetching heros...');
return this.heros;
}
}
以上代码运行后会抛出以下异常信息:
Uncaught Error: Can't resolve all parameters for HeroService: (?).
上面异常信息说明无法解析 HeroService
的所有参数,而 HeroService
服务的构造函数如下:
export class HeroService {
constructor(private loggerService: LoggerService) { }
}
这是因为构造函数中的类型信息并没有保存。
这时就要用到注入器
import { Injectable } from '@angular/core';
import { LoggerService } from './logger.service';
@Injectable()
export class HeroService {
// ...
}
这样就可以成功运行了。
参考
http://www.semlinker.com/categories/angular/
https://www.angular.cn/guide/dependency-injection