更新 2018-03-24

ng 是不允许循环依赖的

abc.ts 

@Injectable()
export class AbcService {
constructor(
private xyzService : XyzService
) {}
} xyz.ts
@Injectable()
export class XyzService {
constructor(
private AbcService : AbcService
) { }
}

A 服务依赖 B 服务, b 服务又依赖 A 服务. 这样是不行的.

如果你非要不可, 可以使用 Injector + settimeout 来处理 (不推荐)

@Injectable()
export class AbcService {
constructor(
@Inject(forwardRef(() => XyzService)) // 一定要加上 forwardRef, 因为 XyzService.ts 使用到了 AbcService
private xyzService : XyzService
) { }
}
@Injectable()
export class XyzService {
constructor(
private injector: Injector
) {
Promise.resolve().then(() => {
this.abcService = injector.get(AbcService);
});
}
abcService: AbcService
}

通过延迟注入, 打破循环.

forwardRef 在上面起到了一个重要的作用. 当我们要注入一个类,但这个类文件里也使用到了我们当前的类时,我们就必须加上 forwardRef
比如, Child inject Parent, Parent viewchild 获取了 Child, 也是需要用 forwardRef
 

refer :

http://blog.thoughtram.io/angular/2016/09/15/angular-2-final-is-out.html ( search Dependency Injection )

小说明 :

大致流程 : 我们负责写 providers, angular 会维护好 injector, 当我们声明需要 service 时, injector 会依据我们的 provider 来创建出 service

单列 : 一个 service 在一个 injector 里是单列.

查找逻辑 : injector 有父子关联, 如果子 injector 没有发现 provider 那么它会去父 injector 找, 和 js prototype 差不多概念.

component + DI : angular 为每一个 component 创建了 injector, 然后它们有父子串联的关系.

4 种方式设置 providers 

1. useClass

providers: [{ provide: AppService, useClass: AppService }]
providers: [AppService]

如果我们的 service 是一个有类的对象, 那么我们可以使用 useClass 或者是它的缩写版, 这也是最常使用的一种 providers 方式.

2.useValue, 当 service 不是一个 class 对象, 就可以用这个, stringOrToken 之后讲.

@Component({
selector: 'my-app',
template: '<h1>My First Angular App</h1>',
providers: [{ provide : "stringOrToken", useValue : "xxx" }]
})
export class AppComponent {
constructor( @Inject("stringOrToken") private service: string) {}
ngOnInit() {
console.clear();
console.log(this.service); //xxx
}
}

3. useExisting

用途 refer : http://blog.thoughtram.io/angular/2016/09/14/bypassing-providers-in-angular-2.html

@Component({
selector: 'my-app',
template: '<h1>My First Angular App</h1>',
providers: [
{ provide: "stringOrToken", useValue: "xxx" },
{ provide: "otherString", useExisting: "stringOrToken" }
]
})
export class AppComponent {
constructor( @Inject("otherString") private service: string) {}
ngOnInit() {
console.clear();
console.log(this.service); //xxx
}
}

简单说就是让你用不同的 "名字" 注入同一个 service.

4. useFactory

@Component({
selector: 'my-app',
template: '<h1>My First Angular App</h1>',
providers: [
AppService,
{
provide: "stringOrToken", useFactory: (appService) => {
console.log(appService);
return "zzz";
}, deps: [AppService]
}
]
})
export class AppComponent {
constructor( @Inject("stringOrToken") private service: string) {}
ngOnInit() {
console.clear();
console.log(this.service); //zzz
}
}

如果创建 service 过程相对复杂可以使用 Factory.

如果注入 service

constructor(private appService: AppService) { }
constructor( @Inject(AppService) private appService) { }
constructor( @Inject("token") private appService) { }

第一种是 TypeScript 模式, 只是一种简化的写法 ( 只能注入用类声明的 service ), @Inject 才是王道

token vs string 

token 的好处是防止命名冲突. angular 提供了 OpaqueToken 方便我们使用

let token = new OpaqueToken("test");
token === token2; //false
providers: [
{
provide: token, useValue: "ttc"
}
] constructor( @Inject(token) private service: string) { }

@Optional

Optional 表示可有可无, 如果没有使用 Optional, 在没有provider 而尝试注入 service 的情况下, angular 是会报错的哦.

constructor(@Optional() private service: AppService) {
console.clear();
console.log(this.service); //null
}

理解组件的父子和宿主关系, @Host 和 viewProviders (在做 transclude 的时候尤其需要懂哦)

@Component({
selector: 'child-a',
template: `
<child-b>
<child-c></child-c>
</child-b>
`,
providers: [],
})

父子关系 : child-a 的模板, 的第一层组件是 child-a 的孩子, 所以 child-b 的父组件是 child-a ( child-a, child-b 是父子关系 )

宿主关系 ( no transclude) : child-a 的宿主就是 child-a, child-b 就是 child-b

宿主关系 ( transclude ) : child-b 的第一层 transclude 组件的宿主是 child-b, 所以 child-c 的 host is child-b

constructor( @Host() private serviceA: ServiceA) { }

@Host() 可以限制 injector 向上查找的范围, 终止与宿主.

例子 :

child-c 使用了 @Host() 那么它就只能获得 child-c, child-b 的 providers, child-a 的就拿不到了. ( 在注入的时候去限制范围 )

child-b 使用了 @Host() 那么它就只能获取 child-b 的 providers. 上面的都拿不到了.

viewProviders 也是用来限制范围的 ( 在提供的时候去限制范围 )

viewProviders 的 service 不能被宿主的 transclude 组件访问, child-b 的 viewProviders, child-c 是 inject 不到的.

multi 

@Component({
selector: 'child-a',
template: `
<div>123</div>
`,
providers: [
{ provide: "datas", useValue: "10", multi: true }, //2个都要放 multi : true 哦, 不然会报错
{ provide: "datas", useValue: "20", multi : true }
],
})
export class ChildAComponent {
constructor( @Inject("datas") private datas) {
console.log(this.datas); // ["10","20"]
}
}

就不用解释了吧.

注入父组件, @SkipSelf(), forwardRef

refer : https://angular.cn/docs/ts/latest/cookbook/dependency-injection.html#!#parent-tree

官网给了一个很好的例子参考.

angular 会把组件放入 injector 中, 所以我们可以通过注入获取父组件.

如果是遇到递归组件的时候 SkipSelf 可以跳过自己这个组件, .

constructor( @SkipSelf() @Optional() public parent: Parent ) { }

而 forwardRef 则可以打破循环依赖.

providers:  [{ provide: Parent, useExisting: forwardRef(() => BarryComponent) }]

以后才说细节吧.

更新 : 2017-01-28

-体会

虽然明白依赖注入的使用和好处,但是一直没有体会到. 今天有点感触.

我们把所有的 service 都丢进一个大染缸里头, 每一个 service 都互相依赖. 这时我们想要其中一个 service 的时候, 我们希望有个人能帮我们把依赖一个接一个的找出来弄美美给我们.

这就是依赖注入做的事情. 帮我们管理好依赖.

除此之外, angular 的依赖注入还有分层的概念. 分层主要的目的是 override 和限制范围.

- module provider vs component provider

module 上定义的 provider 会定义到 root injector 上, 是全局使用的 (如果 module 不幸被 lazy load 那么它就不会在 root injector 了而是 child, 具体哪个 child 就要看是第几层的 lazy load 咯)

component 上定义的 provider 就只有 component 和它的 child component 能使用.

-直接使用依赖注入

export class AdminComponent implements OnInit {
constructor(private injector : Injector) {
console.log(injector); //获取当前 component's injector let symbol = new OpaqueToken("valueKey");
let providers = [{ provide : symbol, useValue : "keatkeat" }];
let parentInjector = ReflectiveInjector.resolveAndCreate(providers);
let child = parentInjector.resolveAndCreateChild([]); //如果想的话, 这里可以 override
console.log(child.get(symbol)); //获取 service
}
ngOnInit() { }
}

用例子说话 :

所有写在 module 的 provides 都会去到 rootInjector 里头 ( lazy load 例外 )

let allModuleProviders = [A,B];
let rootInjector = ReflectiveInjector.resolveAndCreate(allModuleProviders);

每一个 component 都会另外创建属于自己的 injector, 并且集成 parent injector

let componentInjecter = rootInjector.resolveAndCreateChild([]); //使用 rootInjecor 创建 child injector

component 里头如果还有 child component, 同样会继续创建子孙 injector 以此类推

let childComponentInjector = componentInjecter.resolveAndCreateChild([]);

当我们 inject service 时, 同一个 injector 只会初始化 service 一次, 单列

let allModuleProviders = [A,B];
let rootinjector = ReflectiveInjector.resolveAndCreate(allModuleProviders);
let a = rootinjector.get(A); // A service init
let a1 = rootinjector.get(A); // A service not init anymore.
console.log(a === a1); //true

child injector 如果没有 override providers 那么它会使用 parent injector 的 providers, 依然会使用同一个 instance

let componentInjecter = rootInjecter.resolveAndCreateChild([]); // no override any providers
let a2 = componentInjecter.get(A); // A service not init anymore.
console.log(a2 === a1); //true

如果 child injector override providers 则会创建新的 instance

let componentInjecter = rootInjecter.resolveAndCreateChild([A]); // override providers
let a2 = componentInjecter.get(A); // A service init
console.log(a2 === a1); //false

寻找依赖只能在同级或祖先级 providers 里.

假设 A service 依赖 B service

let allModuleProviders = [A];
let rootInjecter = ReflectiveInjector.resolveAndCreate(allModuleProviders); let componentInjecter = rootInjecter.resolveAndCreateChild([B]);
let a = componentInjecter.get(A); // fail, provider not found

我们在 child injector provide B 是没办法让 A 找到它的.

如果同级就可以

let allModuleProviders = [A,B];

祖先级也可以

let allModuleProviders = [B];
let rootInjecter = ReflectiveInjector.resolveAndCreate(allModuleProviders); let componentInjecter = rootInjecter.resolveAndCreateChild([A]);
let a = componentInjecter.get(A); // pass

A 能往上找到 B.

angular2 学习笔记 ( DI 依赖注入 )的更多相关文章

  1. Asp.net core 学习笔记 ( DI 依赖注入 )

    比起 Angular 的依赖注入, core 的相对简单许多, 容易明白 所有 provider 都在 startup 里配置. public void ConfigureServices(IServ ...

  2. SpringMVC:学习笔记(11)——依赖注入与@Autowired

    SpringMVC:学习笔记(11)——依赖注入与@Autowired 使用@Autowired 从Spring2.5开始,它引入了一种全新的依赖注入方式,即通过@Autowired注解.这个注解允许 ...

  3. Java开发学习(六)----DI依赖注入之setter及构造器注入解析

    一.DI依赖注入 首先来介绍下Spring中有哪些注入方式? 我们先来思考 向一个类中传递数据的方式有几种? 普通方法(set方法) 构造方法 依赖注入描述了在容器中建立bean与bean之间的依赖关 ...

  4. Spring.NET学习笔记6——依赖注入(应用篇)

    1. 谈到高级语言编程,我们就会联想到设计模式:谈到设计模式,我们就会说道怎么样解耦合.而Spring.NET的IoC容器其中的一种用途就是解耦合,其最经典的应用就是:依赖注入(Dependeny I ...

  5. AngularJS学习笔记之依赖注入

    最近在看AngularJS权威指南,由于各种各样的原因(主要是因为我没有money,好讨厌的有木有......),于是我选择了网上下载电子版的(因为它不要钱,哈哈...),字体也蛮清晰的,总体效果还不 ...

  6. Angular4.0从入门到实战打造在线竞拍网站学习笔记之三--依赖注入

    Angular4.0基础知识之组件 Angular4.0基础知识之路由 依赖注入(Dependency Injection) 正常情况下,我们写的代码应该是这样子的: let product = ne ...

  7. [学习笔记]Spring依赖注入

    依赖: 典型的企业应用程序不可能由单个对象(在spring中,也可称之bean)组成,再简单的应用也是由几个对象相互配合工作的,这一章主要介绍bean的定义以及bean之间的相互协作. 依赖注入: s ...

  8. Java开发学习(七)----DI依赖注入之自动装配与集合注入

    一.自动配置 上一篇博客花了大量的时间把Spring的注入去学习了下,总结起来就两个字麻烦.麻烦在配置文件的编写配置上.那有更简单方式么?有,自动配置 1.1 依赖自动装配 IoC容器根据bean所依 ...

  9. Unity学习笔记(4):依赖注入

    Unity具体实现依赖注入包含构造函数注入.属性注入.方法注入,所谓注入相当赋值,下面一个一个来介绍 1:构造函数注入 1.1当类有多个构造函数时,可以通过InjectionConstructor特性 ...

随机推荐

  1. c语言:蜗牛的爬行。

    main() { printf("hello,word!,第一次的c语言输出"); }

  2. Craking the coding interview 面试题:完美随机洗牌

    给定一个序列,随机打乱这个序列,新产生的序列和任意一个序列产生的可能性是一样的,就是所谓的完美随机洗牌. 看下面的运行结果: 上面第一列是原数列,下面一行是新产生的打乱的数列. 基本思想:如果n-1个 ...

  3. 【算法与数据结构】在n个数中取第k大的数(基础篇)

    (转载请注明出处:http://blog.csdn.net/buptgshengod) 题目介绍            在n个数中取第k大的数(基础篇),之所以叫基础篇是因为还有很多更高级的算法,这些 ...

  4. Swift3.0已出坑-适配iOS10,项目迁移Swift3.0问题总结。

    http://www.jianshu.com/p/27fd2a2b32e4 Yes表示swift版本为2.3 NO表示swift版本为3.0

  5. lucene4.5近实时搜索

    近实时搜索就是他能打开一个IndexWriter快速搜索索引变更的内容,而不必关闭writer,或者向writer提交,这个功能是在2.9版本以后引入的,在以前没有这个功能时,必须调用writer的c ...

  6. 让你的WizFi250适应各种气候

    这篇文章会具体描写叙述如何马上得到指定城市的天气状况(比方首尔).由OpenWeatherMap提供. 用JSON(由OpenWeatherMap提供),XML和一个以太网模块.使WIZnet-Wiz ...

  7. 模仿ios下的coverflow

    Android高级图片滚动控件,编写3D版的图片轮播器 http://blog.csdn.net/guolin_blog/article/details/17482089 A cool Open So ...

  8. Ouath协议

    OAuth是一个开放协议,允许用户让第三方应用以安全且标准的方式获取该用户在某一网站.移动或桌面应用上存储的私密的资源(如用户个人信息.照片.视频.联系人列表),而无需将用户名和密码提供给第三方应用. ...

  9. call, apply, bind作用

    call, apply作用就是(改变方法中的this指向)借用别人的方法来调用,就像调用自己的一样 function Person(name) { this.name = name; } Person ...

  10. vedeo与audio标签的使用

    浏览器原生支持音视频无疑是一件大事——尤其对移动设备而言.不依赖Flash,意味着更加省电.安全和快速的播放体验,而且只需要引入一个标签,就能播放自如. <video src="dao ...