发表于: 2017-06-24 21:34:24
0 831
反正已经写了四遍都没有了。不想再写了。
今天完成的事情
依赖注入部分已经写完了
打算实现以下揭秘最后面的小项目,自己搭的框架和书上的不一样,先按照自己的来吧,不行的话再说吧。
明天计划事情:
UI组件用的和书上的也不一样,不过刚才写的一个小demo,没有什么效果,眼镜受不了了。。。
遇到的问题
额。。刚才写的是什么来着。。
收获
完整的依赖注入已经放到简书上面去了:http://www.jianshu.com/p/1159cdddde11
二、 Angular依赖注入
在介绍Angular依赖注入之前,先来理一下三个概念:
- 注入器(Injector):就想制造工厂,提供了一系列的接口,用于创建依赖对象的实例。
- 提供商(Provider):用于配置注入器,注入器通过它来创建被依赖对象的实例,Provider把令牌(Token映射到工厂方法,被依赖的对象就是通过这个方法创建的。
- 依赖(Denpendence):指定了被依赖对象的类型,注入器会根据此类型创建对应的对象。
说了半天到底是什么样的?
用代码示例如下:
var injector = new Injector(...);var robot = injector.get(HRobot);robot.cook();
Injector()
的实现如下:
import { ReflecttiveInjector } form '@angular/core';var injector = ReflectiveInjector.resolveAndCreat([{provide: HRobot, useClass: HRobot},{provide: Meat, useClass: Meat},{provide: Salad, useClass: Salad}]);
还有注入器是这样知道知道初始化HRobot
需要依赖Meat
和Salad
:
export class Robot {//...consructor(public meat: Meat, public salad: Salad) {}//...}
当然,看了头大是应该的,因为上面的东西压根就不需要自己动手写,Angular
的依赖注入框架已经自动帮我们完成了(注入器的生成和调用)。
1. 在组件中注入服务Angular
在底层做了大量的初始化工作,这极大地降低了我们使用依赖注入的成本,现在要完成依赖注入,我们只需要三步:
- 通过
import
导入被依赖的对象服务 - 在组件中配置注入器。在启动组件时,
Angular
会读取@Component
装饰器里的providers
元数据,它是一个数组,配置了该组件需要使用的所有依赖,Angular
的依赖注入框架会根据这个列表去创建对应的示例。 - 在组件构造函数中声明需要注入的依赖。注入器会根据构造函数上的声明,在组件初始化时通过第二步中的
providers
元数据配置依赖,为构造函数提供对应的依赖服务,最终完成依赖注入。
例子来了:
// app.component.ts//...// 1. 导入被依赖对象的服务import { MyService } from './my-service/my-service.service';@Component({//...// 2. 在组件中配置注入器providers: [MyService]//...})export class AppComponent {// 3. 在构造函数中声明需要注入的依赖constructor(private myService: MyService) {}}
2. 在服务中注入服务
除了组件依赖服务,服务间依的相互调用也很寒常见。例如我们想给我们的汉堡机器人加上一个计数器,来记录它的生产状况,但是计数器又依靠电源来工作,我们就可以用一个服务来实现:
// power.service.tsimport { Injectable } from '@angular/core';@Injectable()export class PowerService {// power come from here..}// count.service.tsimport { Injectable } from '@angular/core';import { PowerService } from './power/power.service';@Injectable()export class CountService {constructor(private power: PoowerService) {}}// app.component.ts 这里是当前组件,其实模块中的注入也一样,后面讲到//...providers: [CountService,PowerService]
这里需要注意的是@Injectable
装饰器是非必须的,因为只有一个服务依赖其他服务的时候才必须需要使用@Injectable
显式装饰,来表示这个服务需要依赖,所以我们的PowerService
并不是必须加上@Injectable
装饰器的,可是,Angular
官方推荐是否依赖其他服务,都应该使用@Injectable
来装饰服务。
3. 在模块中注入服务
在模块中注册服务和在组件中注册服务的方法是一样的,只是在模块中注入的服务在整个组件中都是可用的。
// app.module.tsimport { BrowserModule } from '@angular/platform-browser';import { NgModule } from '@angular/core';import { AppComponent } from './app.component';@NgModule({declarations: [AppComponent,],imports: [BrowserModule],providers: [CountService, PowerService],bootstrap: [AppComponent]})export class AppModule { }
与在组件中注入不同的是,在Angular
应用启动的时候,它好首先加载这个模块需要的所有依赖,,此时会生成一个全局的根注入器,由该依赖创建的依赖注入对象会再整个应用中可见,并共享一个实例。Angular
没有模块级作用域这个概念,只有应用程序级作用域和组件级作用域,这种设计主要是考虑模块的扩展性,一个应用通常由多个模块合并和成,在@NgModule
中注册的服务,默认在整个应用中可用。
下面说两种特殊情况:
假设在两个模块中使用同样的
Token
注入了同一个服务,并且这两个模块先后导入到了根组件中:// ...@NgModule({imports: [AModule,BModule]// ...})那么后面导入的模块中的服务会覆盖前面导入模块中的服务,也就是说
BModule
中的服务会覆盖AModule
中的服务,即使是在AModule
中注入的服务,同样使用的是BMoudle
中提供的实例。还是假设两个模块同样使用同一个
Token
注入了同一个服务,但是BModule
模块是导入在AModule
模块中的:// a.module.ts// ...@NgModule({imports: [BModule]})那么这种情况下两个模块使用的都是
AModule
中注入的服务。可以推断出在根模块中注入的服务是拥有最高优先级的,你可以在任何地方放心使用。
三、Provider
1. Provider的理解
Provider
是有必要单独提出来一节的,上面第二节中我们其实只是简单的使用了其中一种的provider
下面来详细说一下Provider
在Angular
中,Provider
描述了注入器(Injector)如何初始化令牌(Token)所对应的依赖服务。Provider
一个运行时的依赖,注入器依靠它来创建服务对象的实例。
比如我们上面用到的例子:
// ...@Component({//...// 2. 在组件中配置注入器providers: [MyService]//...})
实际上它的完整形式应该是这样的:
@Component({//...// 2. 在组件中配置注入器providers: [{provide: MyService, useClass: MyService}]//...})
所以说我们上面只使用了一种provider
: 类Provider(ClassProvider
)。
2. Provider注册方式
上面提到我只使用了其中一种注册方式,那么下面介绍Angular
中提供的四中常见的注册方式:
- 类Provider(
ClassProvider
) - 值Provider(
ValueProvider
) - 别名Provider(
ExistingProvider
) - 工厂Provider(
FactoryProvider
)
类
Provider
类Provider
基于令牌(Token
)指定依赖项,这种方式可是让依赖被动态指定为其他不同的具体实现,只要接口不变,对于使用方就是透明的。比如数据渲染服务(Render
),Render
服务对上层提供的接口是固定的,倒是底层的渲染方式可以不同:var inject = Injector.resolveAndCreate([{provide: Render, useClass: DomRender}//{provide: Render, useClass: DomRender} // canvas 渲染方式//{provide: Render, useClass: DomRender} // 服务的想染方式])// 调用方不用做任何修改class AppComponent {construtor(private render: Render) {}}值
Provider
由于依赖的对象并不一定都是类,也可以是字符串、常量、对象等其他数据类型的,这可以方便用在全局变量、系统相关参数配置场景中。在创建Provider
对象的时候,只需要使用useValue
就可以声明一个值Provider
:let freeMan = {freeJob: boolen;live: () => {return 'do something u cant do'}};@Component({// ...providers: [{provide: 'someone', useValue: freeMan}]})别名
Provider
有了别名Provider
,我们就可以在一个Provider
中配置多个令牌(Token
),其对于的对象指向同一个实例,从而实现了多个依赖、一个对象实例的作用:// ...providers: [{provider: Power1, useClass: PowerService},{provider: Power2, useClass: PowerService}]// ...仔细想想,这样对吗?
显然是不对的,如果两个都使用了useClass
那么按照令牌,将会创建两个不同的实例出来,那么应该怎么实现两个令牌同一个实例呢?答案是使用useExistiong
:// ...providers: [{provider: Power1, useClass: PowerService},{provider: Power2, useExisting: PowerService}]// ...工厂
Provider
工厂Provider
允许我们根据不同的条件来实例化不同的服务,比如,我们在开发环境需要打印日志,但是在实际部署的时候可能并不需要打印这些东西,那么我们总不可能去找到整个应用中所有的console.log()
这样的方法吧,这个时候我们可以使用工厂provider
来帮我们处理,我们只需要在工厂provider
中设定一个条件,使其能够根据条件返回实例化我们需要的服务就可以了。为了实现这样的功能我们可以在根模块中这样注入:// app.module.ts@NgModule({// ...providers: [HeroService,ConsoleService,{provide: LoggerService,useFactory: (consoleService) => {return new LoggerService(true, consoleService);},deps: [ConsoleService]}],bootstrap: [AppComponent]})export class AppModule { }哦哦,那两个服务是这样写的:
// console.service.ts// ...export class ConsoleService {log(message) {console.log(`ConsoleService: ${message}`);}}// logger.service.ts// ...export class LoggerService {constructor(private enable: boolean,consoleService: ConsoleService) { }log(message: string) {if (this.enable) {console.log(`LoggerService: ${message}`);}}}然后在组件构造函数中写上需要的服务就好。
四、限定方式的依赖注入
想象一场景,你应用中的某个服务的provider
被当做无效代码删掉了,那么你的应用可能就会出问题。还好这个问题早在设计的时候就已经考虑到了,我们可以使用Angular
提供的@Optional
和@Host
装饰器来解决这个问题。Optional
可以兼容依赖不存在的情况,提高系统的健壮性;@Host
可以限定查找规则,明确实例化的位置,避免一些莫名的共享对象问题。
@Optional
借助@Optional
就可以实现可选注入:
// app.component.ts// ...import { Optional } from '@angular/core';constructor(@Optional() private logger: LoggerService) {if (this.logger) {this.logger.log('i am choosed');}}
像例子中的那样只需要在宿主组件(Host Component)的构造函数中增加@Optional
装饰器即可。
需要注意的是,上面例子中的LoggerService
并不是不存在,只是并没有根据providers
元数据中配置被实例化出来。
@HostAngular
中依赖查找的规则是按照注入器从当前组件向父组件查找,直到找到要注入的依赖位置,如果找不到就会报错。我们可以使用Angular
提供的@Host
装饰器来解决 这个问题。
宿主组件如果一个组件注入了依赖项,那么这个组件就是这个依赖的宿主组件;如果这个组件通过<ng-content>
被嵌入到了父组件,那这个父组件就是这个依赖的宿主组件。
- 宿主组件是当前组件
我们给组件构造函数加上@Host
装饰器:// ...@Component({selector: 'parent',template: `<h1>这里是父组件</h1>`})constructor(@Host()logger: LoggerService) {}// 加上@Host之后会报错,因为我们并没有在这个组件中注入LoggerService// 但是我们可以加上@Optional来避免报错//@Host()//@Optional()//logger: LoggerService) {}) - 宿主组件是父组件
我们修改一下上面的组件为父组件:
增加一个子组件:// parent.component.ts// ...@Component({selector: 'parent',template: `<h1>这里是父组件</h1><ng-content></content>`// 在父组件中注入 LoggerServiceproviders: [LoggerService]})constructor() {}
当然// child.component.ts// ...@Component({selector: 'child',template: `<h1>这里是子组件</h1>`})constructor(@Host()@Optional()logger: LoggerService)){}<parent>
标签中应该这样写:
因为此时宿主组件是父组件,所以我们在父组件中注入<parent><child></child></parent>LoggerService
Angular
注入器会自动向上查找,找到ParentComponet
中的配置,从而完成注入。
评论