angular11源码探索六[服务基础一]
服务
Angular依赖项注入现在是Angular的核心部分,并允许将依赖项注入到组件或类中
依赖注入(DI)是一种技术,在这种技术中,我们将一个对象的实例提供给另一个依赖于它的对象。这种技术也称为“控制反转”(IoC)
IoC — 控制反转
DI — 依赖注入
IOC
三个原则
- 高层次的模块不应该依赖于低层次的模块,它们都应该依赖于抽象
- 抽象不应该依赖于具体实现,具体实现应该依赖于抽象
- 面向接口编程 而不要面向实现编程
IOC,是一种设计原则:
通过将组件(Components)的设置和使用分开,来降低类别或模组之间的耦合度(即,解耦)
控制反转,指实例依赖成员的[控制流程],由主动控制变成被动控制,因此被称为控制反转
案例:
- 打开iPhone,打开微信,进入群聊,抢红包
- 打开iPhone,打开短信,编辑短信内容,选择收件人,发送短信
- 打开iPhone,打开小米商城,选择需要抢购的商品,等待到达抢购时间,开始抢购
这些过程和iPhone耦合的很紧密,如果小明需要换手机了,那么那需要在每个过程中都将iPhone换为新手机。这时,就需要控制反转,我们不再主动去获得手机,而是被动的接收一个手机。
接收手机
- 打开手机,打开微信,进入群聊,抢红包
- 打开手机,打开短信,编辑短信内容,选择收件人,发送短信
- 打开手机,打开小米商城,选择需要抢购的商品,等待到达抢购时间,开始抢购
我们发现,过程和具体的一个手机(例如,iPhone)耦合度降低了,可以很简单的更改手机的实例。
另一个例子
我们去网咖打游戏,是不可能自己带游戏去网咖的,而是网咖都把这些游戏提供好了
需要的游戏,不用自己下载,而是网咖提供给你。
==>
需要的依赖实例,不用主动(Active)建立,而是被动(Passive)接收。
简单来说,A依赖B,但A无法控制B的创建和销毁,仅使用B,那么B的控制权交给C(A以外)处理
- 第一个例子,小明是A,依赖手机B,C可以是其他任何给小明手机的人(可能有点牵强)
- 第二个例子,我们是A,依赖游戏B,C则是网吧
依赖注入DI
刚才那两个例子中,小明需要手机,我们需要玩游戏,那么有哪些方法可以让我们被动接收这些东西呢?假设小明是A,手机是B
- 通过A的接口,把B传入
- 通过A的构造,把B传入
- 通过设置A的属性,把B传入
这个过程就是依赖注入,即A啥都没干,就可以直接使用B,比如我们啥也不用干,就可以直接玩游戏,真香。
也可以说,依赖注入是实现控制反转的一种方式,它们之间有着密切的联系
案例一
class Id {
static getId(type: string): Id {
return new Id()
}
}
class Address {
constructor(city, street) { }
}
class Person {
id: Id
address: Address
constructor(id: Id, address: Address) {
this.id = id
this.address = address
}
}
// 在某个类当中调用的时候
main() {
const id = Id.getId('123')
const address = new Address('北京', '北京')
const person = new Person(id, address)
}
我们也将依赖提升到了入口处的 main()
当中,但是在当下这种形式中,我们已经知道如果有新的需求变动,我们还是需要去模块的内部来进行修改,下面我们就来看看如何在 Angular
当中来解决这个问题的
在 Angular
的依赖注入中主要有三个概念
Injector
,注入者,利用其提供的API
去创建依赖的实例Provider
,告诉Injector
如何去创建一个实例(构造这个对象)Object
,创建好的对象,也就是当前所处的模块或者组件需要的依赖(某种类型的对象,依赖本身也是一种类型)
小案例demo
服务
@Injectable()
export class StateNewService {
constructor() {}
getLog() {
return '1111'
}
}
模块 注入
@NgModule({
providers:[StateNewService]
})
页面使用
export class TwoComponent implements OnInit {
constructor(private states:StateNewService) {
console.log(states.getLog());
}
}
类型令牌
StateNewService
用作搜索查找的令牌,然后我们新类StateNewService
,组件可以使用它
providers:[{ provide: StateNewService, useClass: StateNewService }]
其实也等价于下面这种写法
[{
provide:StateNewService,
useFactory:()=>new StateNewService()
}]
替换当前class
@Injectable()
export class CowService {
makeNoise() {
return 'mooo!';
}
}
@Injectable()
export class TestService {
constructor() { }
makeNoise() {
return '333';
}
}
providers: [
{provide:TestService,useClass:CowService}
]
使用的时候
constructor(
private other: TestService
) {
console.log(other.makeNoise()); // mooo!
字符串令牌
依赖项是值或者对象或者类表示
服务
@Injectable()
export class StateNewService {
constructor() {}
getLog() {
return '1111'
}
}
模块
@NgModule({
providers:[
{ provide: 'STATE_NEW_SERVICE', useClass: StateNewService },
{provide:'APIURL', useValue: 'http://SomeEndPoint.com/api' },
{provide:'USE_FAKE', useValue: true },
]
})
使用
export class TwoComponent implements OnInit {
constructor(
@Inject('STATE_NEW_SERVICE') private states: StateNewService,
@Inject('APIURL') private apiURL:string,
@Inject('USE_FAKE') private use:boolean ) {
console.log(states.getLog());
// two.component.ts:26 1111
console.log(apiURL);
// two.component.ts:27 http://SomeEndPoint.com/api
console.log(use);
// true
}
}
字符串令牌更易于错误键入,这使得在大型应用程序中难以跟踪和维护。
Angular提供了InjectionToken
类,以确保创建了唯一令牌。通过创建类的新实例来创建注入令牌InjectionToken
。
InjectionToken
- 创建
Token
的时候为了避免命名冲突,尽量避免使用字符串作为Token
- 若要创建模块内通用的依赖对象,需要在
NgModule
中注册相关的provider
- 若在每个组件中都有唯一的依赖对象,就需要在
Component
中注册相关的provider
首先,创建一个单独的文件,并将其命名为tokens.ts
InjectionToken
从@angular/core
库导入。创建的实例InjectionToken
并将其分配给constAPI_URL
import { InjectionToken } from '@angular/core';
export const API_URL= new InjectionToken<string>('');
- 在module,令牌用于在
providers
元数据中注册值
import { API_URL } from './tokens';
providers: [
{ provide: API_URL, useValue: 'http://SomeEndPoint.com/api' }
]
-
直接使用
import { API_URL } from './tokens'; constructor(@Inject(API_URL) private apiURL: string) { }
你可以把multi:true
打印的时候之前是字符串现在你发现返回的是数组,原理就是相同的token
注册多个值
providers:[
{provide:API_URL, useValue: 'http://SomeEndPoint.com/api',multi:true },
]
当我们给令牌添加第二个token
providers:[
{provide:API_URL, useValue: 'http://SomeEndPoint.com/api1',multi:true },
{provide:API_URL, useValue: 'http://SomeEndPoint.com/api2',multi:true },
]
constructor(
@Inject(API_URL) private apiURL:string
) {
console.log(apiURL);
// ["http://SomeEndPoint.com/api1", "http://SomeEndPoint.com/api2"]
我们发现如果移除其中一个multi:true
,我们会发现打印的时候报错
如果发现如果两个都移除multi:true
,我们会发现打印的是api2
,下面的会把上面的替换掉
决定自己的高度的是你的态度,而不是你的才能
记得我们是终身初学者和学习者
总有一天我也能成为大佬