angular2 学习笔记 ( DI 依赖注入 )
更新 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 依赖注入 )的更多相关文章
- Asp.net core 学习笔记 ( DI 依赖注入 )
比起 Angular 的依赖注入, core 的相对简单许多, 容易明白 所有 provider 都在 startup 里配置. public void ConfigureServices(IServ ...
- SpringMVC:学习笔记(11)——依赖注入与@Autowired
SpringMVC:学习笔记(11)——依赖注入与@Autowired 使用@Autowired 从Spring2.5开始,它引入了一种全新的依赖注入方式,即通过@Autowired注解.这个注解允许 ...
- Java开发学习(六)----DI依赖注入之setter及构造器注入解析
一.DI依赖注入 首先来介绍下Spring中有哪些注入方式? 我们先来思考 向一个类中传递数据的方式有几种? 普通方法(set方法) 构造方法 依赖注入描述了在容器中建立bean与bean之间的依赖关 ...
- Spring.NET学习笔记6——依赖注入(应用篇)
1. 谈到高级语言编程,我们就会联想到设计模式:谈到设计模式,我们就会说道怎么样解耦合.而Spring.NET的IoC容器其中的一种用途就是解耦合,其最经典的应用就是:依赖注入(Dependeny I ...
- AngularJS学习笔记之依赖注入
最近在看AngularJS权威指南,由于各种各样的原因(主要是因为我没有money,好讨厌的有木有......),于是我选择了网上下载电子版的(因为它不要钱,哈哈...),字体也蛮清晰的,总体效果还不 ...
- Angular4.0从入门到实战打造在线竞拍网站学习笔记之三--依赖注入
Angular4.0基础知识之组件 Angular4.0基础知识之路由 依赖注入(Dependency Injection) 正常情况下,我们写的代码应该是这样子的: let product = ne ...
- [学习笔记]Spring依赖注入
依赖: 典型的企业应用程序不可能由单个对象(在spring中,也可称之bean)组成,再简单的应用也是由几个对象相互配合工作的,这一章主要介绍bean的定义以及bean之间的相互协作. 依赖注入: s ...
- Java开发学习(七)----DI依赖注入之自动装配与集合注入
一.自动配置 上一篇博客花了大量的时间把Spring的注入去学习了下,总结起来就两个字麻烦.麻烦在配置文件的编写配置上.那有更简单方式么?有,自动配置 1.1 依赖自动装配 IoC容器根据bean所依 ...
- Unity学习笔记(4):依赖注入
Unity具体实现依赖注入包含构造函数注入.属性注入.方法注入,所谓注入相当赋值,下面一个一个来介绍 1:构造函数注入 1.1当类有多个构造函数时,可以通过InjectionConstructor特性 ...
随机推荐
- oracle日期时间函数总结
常常写 sql 的同学应该会接触到一些 oracle 的日期时间函数, 比如: 財务软件或者人力资源软件须要依照每年, 每季度, 每月, 甚至每一个星期来进行统计. 今天闲来没事, 特意从网上整理了一 ...
- [Javascript] Grouping and Nesting Console Log Output
Organize your log output by grouping your logs into collapsable hierarchies using console.group(). ; ...
- C++高精度运算类bign (重载操作符)
大数据操作,有例如以下问题: 计算:456789135612326542132123+14875231656511323132 456789135612326542132123*14875231656 ...
- 查看Linux下网卡状态或 是否连接(转)
1) 通过mii-tool指令 [root@localhost root]# mii-tool eth0: negotiated 100baseTx-FD, link o ...
- 第二篇:智能电网(Smart Grid)中的数据工程与大数据案例分析
前言 上篇文章中讲到,在智能电网的控制与管理侧中,数据的分析和挖掘.可视化等工作属于核心环节.除此之外,二次侧中需要对数据进行采集,数据共享平台的搭建显然也涉及到数据的管理.那么在智能电网领域中,数据 ...
- Oracle 安装中遇到的问题
第一次用甲骨文,这期待!虽然mySQL也是甲骨文的. 去官网下了Oracle G11 R2 X64,本人的电脑是64位的win7,没开防火墙. 按照网上众多的教程,做完安装,可是安装过程不是那么的顺利 ...
- 转载:C# 之泛型详解
本文原地址:http://www.blogjava.net/Jack2007/archive/2008/05/05/198566.html.感谢博主分享! 什么是泛型 我们在编写程序时,经常遇到两个模 ...
- ASP.NET-FineUI开发实践-12
1.网上找到了行合并的示例,extjs写的,我把它挪过来改了下,FineUI也能用,就是只能放着看,选择和编辑行扩展列没有测试,放出来大家看着用吧. <script> F.ready(fu ...
- python面对对象编程----------7:callable(类调用)与context(上下文)
一:callables callables使类实例能够像函数一样被调用 如果类需要一个函数型接口这时用callable,最好继承自abc.Callable,这样有些检查机制并且一看就知道此类的目的是c ...
- HTML5之canvas
canvas的创建,canvas图片的绘制及图片的封装,矩形,矩形边框,圆,线的绘制. JavaScript代码如下:main.js /** * Created by zengkm on 15-9-1 ...