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. 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM