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.