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,下面的会把上面的替换掉

原文地址:https://www.cnblogs.com/fangdongdemao/p/14152885.html