Angular 依賴注入

一白魂、什么是依賴注入

控制反轉(zhuǎn)(IoC)

控制反轉(zhuǎn)的概念最早在2004年由Martin Fowler提出爽篷,是針對面向?qū)ο笤O計不斷復雜化而提出的一種設計原則,是利用面向?qū)ο缶幊谭▌t來降低應用耦合的設計模式壮啊。

IoC強調(diào)的是對代碼引用的控制權由調(diào)用方轉(zhuǎn)移到了外部容器颇蜡,在運行是通過某種方式注入進來,實現(xiàn)了控制反轉(zhuǎn)假颇,這大大降低了程序之間的耦合度胚鸯。依賴注入是最常用的一種實現(xiàn)IoC的方式,另一種是依賴查找笨鸡。

依賴注入(Dependency Injection)

當然姜钳,按照慣例我們應該舉個例子, 哦對形耗,我們主要說明的是依賴注入哥桥,依賴查找請自行查閱資料。

假設我們有一個能做漢堡的設備(HRobot)激涤,需要用肉(meat)和一些沙拉(salad)作為原料拟糕,我們可以這樣實現(xiàn):

export class HRobot {
    public meat: Meat;
    public salad: Salad;
    constructor() {
        this.meat = new Meat();
        this.salad = new Salad();
    }
    cook() {}
}

看一下好像沒有什么問題,可能你已經(jīng)發(fā)現(xiàn),我們的原材料都是放在機器里面的送滞,如果我們想吃別的口味的漢堡恐怕就要去鄉(xiāng)村基了侠草。
為了可以吃到別的口味的漢堡,我們不得不改造一下我們的HRobot:

export class HRobot {
    public meat: Meat;
    public salad: Salad;
    constructor(public meat: Meat, public salad: Salad) {
        this.meat = meat;
        this.salad = salad;
    }
    cook() {}
}

現(xiàn)在犁嗅,只要要直接給它meat和salad就好了边涕,我們的HRobot()并不需要知道給它的是什么樣的meat

let hRobot = new HRobot(new Meat(), new Salad());

比如,我們想吃雞肉漢堡褂微,只需要給它一塊雞肉就好:

class Chicken extends Meat {
    meat = 'chiken';
}

let cRobot = new HRobot(new Chicken(), new Salad());

感覺還不錯功蜓,我們再也不會為了吃一個雞肉漢堡大費周章的去改造一臺機器,這太不可思議了蕊梧。

我可能想到了霞赫,你還是懶得弄塊雞肉給它,這時候可以使用工廠函數(shù):

export class HRobotFactory {
    createHRobot() {
        let robot = new HRobot(this.createMeat(), this.createSalad());
    }
    
    createMeat() {
        return new Meat();
    }
    
    creatSalad() {
        return new Salad();
    }
}

現(xiàn)在有了工廠肥矢,就有源源不斷的漢堡可以吃了端衰,開不開心,驚不驚喜甘改?
好吧旅东,沒有最懶,只有更懶十艾,連工廠都懶得管理我也是無話可說抵代,幸運的是我們有Angular提供的依賴注入框架,它可以讓你伸手就有漢堡吃忘嫉!

二荤牍、 Angular依賴注入

在介紹Angular依賴注入之前,先來理一下三個概念:

  • 注入器(Injector):就想制造工廠庆冕,提供了一系列的接口康吵,用于創(chuàng)建依賴對象的實例。
  • 提供商(Provider):用于配置注入器访递,注入器通過它來創(chuàng)建被依賴對象的實例晦嵌,Provider把令牌(Token)映射到工廠方法,被依賴的對象就是通過這個方法創(chuàng)建的拷姿。
  • 依賴(Denpendence):指定了被依賴對象的類型惭载,注入器會根據(jù)此類型創(chuàng)建對應的對象。

說了半天到底是什么樣的响巢?


依賴注入簡單圖示

用代碼示例如下:

var injector = new Injector(...);
var robot = injector.get(HRobot);
robot.cook();

Injector()的實現(xiàn)如下:

import { ReflecttiveInjector } form '@angular/core';

var injector = ReflectiveInjector.resolveAndCreat([
    {provide: HRobot, useClass: HRobot},
    {provide: Meat, useClass: Meat},
    {provide: Salad, useClass: Salad}
]);

還有注入器是這樣知道知道初始化HRobot需要依賴MeatSalad:

export class Robot {
    //...
    consructor(public meat: Meat, public salad: Salad) {}
    //...
}

當然描滔,看了頭大是應該的,因為上面的東西壓根就不需要自己動手寫踪古,Angular的依賴注入框架已經(jīng)自動幫我們完成了(注入器的生成和調(diào)用)含长。

1. 在組件中注入服務
Angular在底層做了大量的初始化工作靶衍,這極大地降低了我們使用依賴注入的成本,現(xiàn)在要完成依賴注入茎芋,我們只需要三步:

  • 通過import導入被依賴的對象服務
  • 在組件中配置注入器。在啟動組件時蜈出,Angular會讀取@Component裝飾器里的providers元數(shù)據(jù)田弥,它是一個數(shù)組,配置了該組件需要使用的所有依賴铡原,Angular的依賴注入框架會根據(jù)這個列表去創(chuàng)建對應的示例偷厦。
  • 在組件構造函數(shù)中聲明需要注入的依賴。注入器會根據(jù)構造函數(shù)上的聲明燕刻,在組件初始化時通過第二步中的providers元數(shù)據(jù)配置依賴只泼,為構造函數(shù)提供對應的依賴服務,最終完成依賴注入卵洗。

例子來了:

// app.component.ts
//...
// 1. 導入被依賴對象的服務
import { MyService } from './my-service/my-service.service';

@Component({
    //...
    // 2. 在組件中配置注入器
    providers: [
        MyService
    ]
    //...
})

export class AppComponent {
    // 3. 在構造函數(shù)中聲明需要注入的依賴
    constructor(private myService: MyService) {}
}

2. 在服務中注入服務
除了組件依賴服務请唱,服務間依的相互調(diào)用也很寒常見。例如我們想給我們的漢堡機器人加上一個計數(shù)器过蹂,來記錄它的生產(chǎn)狀況十绑,但是計數(shù)器又依靠電源來工作,我們就可以用一個服務來實現(xiàn):

// power.service.ts

import { Injectable } from '@angular/core';

@Injectable()
export class PowerService {
    // power come from here..
}


// count.service.ts

import { 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.ts
import { 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應用啟動的時候啰挪,它好首先加載這個模塊需要的所有依賴信不,,此時會生成一個全局的根注入器亡呵,由該依賴創(chuàng)建的依賴注入對象會再整個應用中可見抽活,并共享一個實例。
Angular沒有模塊級作用域這個概念锰什,只有應用程序級作用域和組件級作用域下硕,這種設計主要是考慮模塊的擴展性丁逝,一個應用通常由多個模塊合并和成,在@NgModule中注冊的服務梭姓,默認在整個應用中可用霜幼。

下面說兩種特殊情況

  • 假設在兩個模塊中使用同樣的Token注入了同一個服務,并且這兩個模塊先后導入到了根組件中:
// ...
@NgModule({
imports: [
    AModule,
    BModule
]
// ...
})

那么后面導入的模塊中的服務會覆蓋前面導入模塊中的服務誉尖,也就是說BModule中的服務會覆蓋AModule中的服務罪既,即使是在AModule中注入的服務,同樣使用的是BMoudle中提供的實例铡恕。

  • 還是假設兩個模塊同樣使用同一個Token注入了同一個服務琢感,但是BModule模塊是導入在AModule模塊中的:
// a.module.ts
// ...
@NgModule({
    imports: [BModule]
})

那么這種情況下兩個模塊使用的都是AModule中注入的服務√饺郏可以推斷出在根模塊中注入的服務是擁有最高優(yōu)先級的驹针,你可以在任何地方放心使用。

三诀艰、Provider

1. Provider的理解
Provider是有必要單獨提出來一節(jié)的柬甥,上面第二節(jié)中我們其實只是簡單的使用了其中一種的provider下面來詳細說一下Provider
Angular中,Provider描述了注入器(Injector)如何初始化令牌(Token)所對應的依賴服務涡驮。Provider一個運行時的依賴暗甥,注入器依靠它來創(chuàng)建服務對象的實例。
比如我們上面用到的例子:

// ...
@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

1. 類Provider
Provider 基于令牌(Token)指定依賴項,這種方式可是讓依賴被動態(tài)指定為其他不同的具體實現(xiàn)棒口,只要接口不變寄月,對于使用方就是透明的。比如數(shù)據(jù)渲染服務(Render)无牵,Render服務對上層提供的接口是固定的漾肮,倒是底層的渲染方式可以不同:
```ts
var inject = Injector.resolveAndCreate([
{provide: Render, useClass: DomRender}
//{provide: Render, useClass: DomRender} // canvas 渲染方式
//{provide: Render, useClass: DomRender} // 服務的想染方式
])

// 調(diào)用方不用做任何修改
class AppComponent {
    construtor(private render: Render) {}
}
```

2. 值Provider
由于依賴的對象并不一定都是類,也可以是字符串茎毁、常量克懊、對象等其他數(shù)據(jù)類型的,這可以方便用在全局變量七蜘、系統(tǒng)相關參數(shù)配置場景中谭溉。在創(chuàng)建Provider對象的時候,只需要使用useValue就可以聲明一個值Provider
```ts
let freeMan = {
freeJob: boolen;
live: () => {return 'do something u cant do'}
};

@Component({
    // ...
    providers: [
        {provide: 'someone', useValue: freeMan}
    ]
})
```

3. 別名Provider
有了別名Provider橡卤,我們就可以在一個Provider中配置多個令牌(Token)扮念,其對于的對象指向同一個實例,從而實現(xiàn)了多個依賴碧库、一個對象實例的作用:

    // ...
    providers: [
        {provider: Power1, useClass: PowerService},
        {provider: Power2, useClass: PowerService}
    ]
    // ...

仔細想想柜与,這樣對嗎巧勤?
顯然是不對的,如果兩個都使用了useClass那么按照令牌弄匕,將會創(chuàng)建兩個不同的實例出來颅悉,那么應該怎么實現(xiàn)兩個令牌同一個實例呢?答案是使用useExistiong:

    // ...
    providers: [
        {provider: Power1, useClass: PowerService},
        {provider: Power2, useExisting: PowerService}
    ]
    // ...
    ```
**4. 工廠`Provider`**
    工廠`Provider`允許我們根據(jù)不同的條件來實例化不同的服務迁匠,比如签舞,我們在開發(fā)環(huán)境需要打印日志,但是在實際部署的時候可能并不需要打印這些東西柒瓣,那么我們總不可能去找到整個應用中所有的`console.log()`這樣的方法吧,這個時候我們可以使用工廠`provider`來幫我們處理吠架,我們只需要在工廠`provider`中設定一個條件芙贫,使其能夠根據(jù)條件返回實例化我們需要的服務就可以了。為了實現(xiàn)這樣的功能我們可以在根模塊中這樣注入:
    ```ts
    // app.module.ts
    @NgModule({
    // ...
    providers: [
        HeroService,
        ConsoleService,
        {
            provide: LoggerService, 
            useFactory: (consoleService) => {
                return new LoggerService(true, consoleService);
            },
            deps: [ConsoleService]
        }
    ],
    bootstrap: [AppComponent]
    })
    export class AppModule { }

哦哦傍药,那兩個服務是這樣寫的:
   ```ts
    // 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}`);
            }
        }
    }

然后在組件構造函數(shù)中寫上需要的服務就好磺平。

四、限定方式的依賴注入

想象一場景拐辽,你應用中的某個服務的provider被當做無效代碼刪掉了拣挪,那么你的應用可能就會出問題。還好這個問題早在設計的時候就已經(jīng)考慮到了俱诸,我們可以使用Angular提供的@Optional@Host裝飾器來解決這個問題菠劝。
Optional可以兼容依賴不存在的情況,提高系統(tǒng)的健壯性睁搭;@Host可以限定查找規(guī)則赶诊,明確實例化的位置,避免一些莫名的共享對象問題园骆。

@Optional
借助@Optional就可以實現(xiàn)可選注入:

// app.component.ts
// ...
import { Optional } from '@angular/core';
constructor(@Optional() private logger: LoggerService) {
    if (this.logger) {
        this.logger.log('i am choosed');
    }
}

像例子中的那樣只需要在宿主組件(Host Component)的構造函數(shù)中增加@Optional裝飾器即可舔痪。
需要注意的是,上面例子中的LoggerService并不是不存在锌唾,只是并沒有根據(jù)providers元數(shù)據(jù)中配置被實例化出來锄码。

@Host
Angular中依賴查找的規(guī)則是按照注入器從當前組件向父組件查找,直到找到要注入的依賴位置晌涕,如果找不到就會報錯滋捶。我們可以使用Angular提供的@Host裝飾器來解決 這個問題。
宿主組件如果一個組件注入了依賴項渐排,那么這個組件就是這個依賴的宿主組件炬太;如果這個組件通過<ng-content>被嵌入到了父組件,那這個父組件就是這個依賴的宿主組件驯耻。

  1. 宿主組件是當前組件
    我們給組件構造函數(shù)加上@Host裝飾器:
    // ...
    @Component({
        selector: 'parent',
        template: `
            <h1>這里是父組件</h1>
        `
    })
    constructor(
        @Host()
        logger: LoggerService) {}
        // 加上@Host之后會報錯亲族,因為我們并沒有在這個組件中注入LoggerService
        
        // 但是我們可以加上@Optional來避免報錯
        //@Host()
        //@Optional()
        //logger: LoggerService) {}
    )
    
  2. 宿主組件是父組件
    我們修改一下上面的組件為父組件:
    // parent.component.ts
    // ...
    @Component({
        selector: 'parent',
        template: `
            <h1>這里是父組件</h1>
            <ng-content></content>
        `
        // 在父組件中注入 LoggerService
        providers: [LoggerService] 
    })
    constructor() {}
    
    增加一個子組件:
    // child.component.ts
    // ...
    @Component({
        selector: 'child',
        template: `
            <h1>這里是子組件</h1>
        `
    })
    constructor(
        @Host()
        @Optional()
        logger: LoggerService) 
    ){}
    
    當然<parent>標簽中應該這樣寫:
    <parent>
        <child></child>
    </parent>
    
    因為此時宿主組件是父組件炒考,所以我們在父組件中注入LoggerService Angular注入器會自動向上查找,找到ParentComponet中的配置霎迫,從而完成注入斋枢。
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市知给,隨后出現(xiàn)的幾起案子瓤帚,更是在濱河造成了極大的恐慌,老刑警劉巖涩赢,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戈次,死亡現(xiàn)場離奇詭異,居然都是意外死亡筒扒,警方通過查閱死者的電腦和手機怯邪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來花墩,“玉大人悬秉,你說我怎么就攤上這事”ⅲ” “怎么了和泌?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長祠肥。 經(jīng)常有香客問我武氓,道長,這世上最難降的妖魔是什么仇箱? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任聋丝,我火速辦了婚禮,結果婚禮上工碾,老公的妹妹穿的比我還像新娘弱睦。我一直安慰自己,他們只是感情好渊额,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布况木。 她就那樣靜靜地躺著,像睡著了一般旬迹。 火紅的嫁衣襯著肌膚如雪火惊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天奔垦,我揣著相機與錄音屹耐,去河邊找鬼。 笑死椿猎,一個胖子當著我的面吹牛惶岭,可吹牛的內(nèi)容都是我干的寿弱。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼按灶,長吁一口氣:“原來是場噩夢啊……” “哼症革!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鸯旁,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤噪矛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后铺罢,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體艇挨,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年韭赘,在試婚紗的時候發(fā)現(xiàn)自己被綠了雷袋。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡辞居,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蛋勺,到底是詐尸還是另有隱情瓦灶,我是刑警寧澤蒂誉,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布扰路,位于F島的核電站央碟,受9級特大地震影響肌括,放射性物質(zhì)發(fā)生泄漏怒炸。R本人自食惡果不足惜匿刮,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一狼牺、第九天 我趴在偏房一處隱蔽的房頂上張望峰档。 院中可真熱鬧禁添,春花似錦撮胧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至铺峭,卻和暖如春墓怀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卫键。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工傀履, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人莉炉。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓钓账,卻偏偏與公主長得像碴犬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子官扣,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1.背景介紹 以前原始社會翅敌,我們需要斧子,然后由于還沒有社會分工惕蹄,只能自己打磨一把來使用蚯涮,對應在程序上是我們需要一...
    LMmx閱讀 339評論 0 2
  • 1.依賴注入(DI) DI是一個設計模式,處理組件如何獲取依賴卖陵。angular中 injector子系統(tǒng)專門負責創(chuàng)...
    梧桐葉_閱讀 1,377評論 1 6
  • 本文作為學習的感悟記錄遭顶,閱讀本文之前需要熟讀相關的官方文檔。 declearable(components, di...
    csRyan閱讀 1,393評論 0 1
  • Angular算是將后端開發(fā)工程化引入前端的先驅(qū)之一,而Dependency injection依賴注入(后面簡稱...
    點融黑幫閱讀 2,248評論 0 5
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理泪蔫,服務發(fā)現(xiàn)棒旗,斷路器,智...
    卡卡羅2017閱讀 134,599評論 18 139