8.《Angular依賴注入》

一勉失、什么是依賴注入

控制反轉(zhuǎn) Inversion of Control (簡(jiǎn)稱IOC) 最早在2004年由Martin Fowler提出卵皂,是針對(duì)面向?qū)ο笤O(shè)計(jì)不斷復(fù)雜化而提出的一種設(shè)計(jì)原則,是一種利用面向?qū)ο缶幊谭▌t來降低應(yīng)用程序耦合的設(shè)計(jì)模式。IoC強(qiáng)調(diào)的對(duì)代碼引用的控制權(quán)由調(diào)用方法轉(zhuǎn)移到外部容器沛贪,在運(yùn)行時(shí)通過某種方式注入進(jìn)來,實(shí)現(xiàn)控制的反轉(zhuǎn),這大大降低了服務(wù)類之間的耦合度利赋。依賴注入是一種最常用的實(shí)現(xiàn)IoC的方式水评。

實(shí)現(xiàn)IOC的框架叫IOC容器,Angular實(shí)現(xiàn)控制反轉(zhuǎn)的手段就是依賴注入

依賴注入 Dependency Injection(簡(jiǎn)稱DI)是用來創(chuàng)建對(duì)象及其依賴的其它對(duì)象的一種方式媚送。 當(dāng)依賴注入系統(tǒng)創(chuàng)建某個(gè)對(duì)象實(shí)例時(shí)中燥,會(huì)負(fù)責(zé)提供該對(duì)象所依賴的對(duì)象(稱為該對(duì)象的依賴)。在依賴注入模式中塘偎,應(yīng)用組件無需關(guān)注所依賴對(duì)象的創(chuàng)建和初始化過程疗涉,可以認(rèn)為框架已初始化好了,開發(fā)者只管調(diào)用即可吟秩。依賴注入有利于應(yīng)用程序中各模塊之間的解耦咱扣,使得代碼更容易維護(hù)。這種優(yōu)勢(shì)可能一開始體現(xiàn)不出來涵防,但隨著項(xiàng)目復(fù)雜度的增加闹伪,各模塊、組件壮池、第三方服務(wù)等相互調(diào)用更頻繁時(shí)偏瓤,依賴注入的優(yōu)點(diǎn)就體現(xiàn)出來了。開發(fā)者可以專注于所依賴對(duì)象的消費(fèi)椰憋,無需關(guān)注這些依賴對(duì)象的產(chǎn)生過程厅克,這將大大提升開發(fā)效率。

二橙依、使用依賴注入的好處

1.松耦合和可重用性
2.提高可測(cè)試性
RealLoginService在其他小組開發(fā)完后才進(jìn)行替換已骇,之前的開發(fā)引用MockLoginService的靜態(tài)代碼進(jìn)行測(cè)試。

提高可測(cè)試性.png

三票编、Angular中如何實(shí)現(xiàn)依賴注入

1.注入器(Inject):

每一個(gè)組件都有一個(gè)注入器實(shí)例褪储,負(fù)責(zé)注入組件需要的對(duì)象,注入器是Angular提供的一個(gè)服務(wù)類慧域,一般情況下不需要直接調(diào)用注入器的方法鲤竹,注入器會(huì)自動(dòng)通過組件的構(gòu)造函數(shù)將組件所需的對(duì)象注入進(jìn)組件。

constructor(private productService:ProductService){...}
此組件的的構(gòu)造函數(shù)聲明了一個(gè)productService屬性昔榴,他的類型是ProductService

Angular的注入器在看到這樣一個(gè)構(gòu)造函數(shù)聲明時(shí)辛藻,其會(huì)在整個(gè)Angular應(yīng)用中去尋找ProductService的實(shí)例,如果能找到這個(gè)實(shí)例互订,那么就會(huì)將ProductService注入到productService里去吱肌。

2.提供器(Provider):

用于配置注入器,注入器通過它來創(chuàng)建被依賴對(duì)象的實(shí)例仰禽,Provider把標(biāo)識(shí)映射到工廠方法中氮墨,被依賴的對(duì)象就是通過該方法創(chuàng)建的纺蛆。

providers:[ProductService]
等同于 providers:[{provide:ProductService, useClass:ProductService}]
provide指定了提供器的token, useClass表示實(shí)例話屬性為new。
providers:[{provider:ProductService, useClass:AnotherProductService}]
providers:[{provide:ProductService, useFactory:()=>{...}]

3.提供器的作用域規(guī)則

  • 當(dāng)一個(gè)提供器聲名在一個(gè)模塊中時(shí)规揪,所有組件都可注入桥氏。
  • 當(dāng)一個(gè)提供器聲名在一個(gè)組件時(shí),只對(duì)此組件和其子組件可見猛铅,其他組件不可注入字支。
  • 當(dāng)聲明在模塊中的提供器和聲明在組件的提供器具有相同的token時(shí),聲明在組件的提供器會(huì)覆蓋聲明在模塊中的提供器奸忽。
  • 一般情況下我們會(huì)優(yōu)先將服務(wù)提供器聲明在模塊中堕伪,只有在服務(wù)必須于某個(gè)組件相對(duì)于其他組件不可見時(shí)才聲明在組件中,而這種情況十分少見栗菜。

四刃跛、在模塊中注入服務(wù)

在根組件中注入這個(gè)服務(wù),所有子組件都能共享這個(gè)服務(wù)苛萎。
在模塊中注入服務(wù)和之前的注入場(chǎng)景稍有不同。Angular在啟動(dòng)程序時(shí)會(huì)啟動(dòng)一個(gè)根模塊检号,并加載它所依賴的其他模塊腌歉,此時(shí)會(huì)生成一個(gè)全局的根注入器,由該注入器創(chuàng)建的依賴注入對(duì)象在整個(gè)應(yīng)用程序級(jí)別可見齐苛,并共享一個(gè)實(shí)例翘盖。同時(shí)根模塊會(huì)指定一個(gè)根組件并啟動(dòng),由該根組件添加的依賴注入對(duì)象是組件樹級(jí)別可見凹蜂,在根組件以及子組件中共享一個(gè)實(shí)例馍驯。

import {NgModule} from '@angular/core'
import {RouterModule} from "@angular/router";
import {FormsModule} from "@angular/forms";
import {BrowserModule} from "@angular/platform-browser";
import {HttpModule} from "@angular/http";

import {rootRouterConfig} from "./app.routes";
import {AppComponent} from "./app.component";



import {ContactService, UtilService} from "./shared";


@NgModule({
  declarations: [
    AppComponent

  ],
  imports     : [BrowserModule, FormsModule, HttpModule, RouterModule.forRoot(rootRouterConfig)],
  providers   : [ContactService, UtilService],
  bootstrap   : [AppComponent]
})
export class AppModule {

}

五、層級(jí)注入

Angular以組件為基礎(chǔ)玛痊,項(xiàng)目開發(fā)中自然會(huì)有層級(jí)嵌套的情況汰瘫,這種組織關(guān)系組成了組件樹。根組件下面的各層級(jí)的子組件擂煞,可以出現(xiàn)在任何層級(jí)的任何組件中混弥,每個(gè)組件可以擁有一個(gè)或多個(gè)依賴對(duì)象的注入,每個(gè)依賴對(duì)象對(duì)于注入器而言都是單例对省。

//生成唯一標(biāo)識(shí)服務(wù)
import {Injectable} from "@angular/core";
@Injectable()
export class Random{
  public num;
  constructor(){
    this.num=Math.random();
  }
}
//子組件A
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
  selector:'contact-a',
  providers:[Random],
  template:`<div>ContactA:{{random.num}}</div>`
})
export class contactAComponent{
  random:Random;
  constructor(r:Random){
    this.random=r;
  }
}
//子組件B
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
  selector:'contact-b',
  providers:[Random],
  template:`<div>ContactB:{{random.num}}</div>`
})
export class contactBComponent{
  random:Random;
  constructor(r:Random){
    this.random=r;
  }
}
//父組件
import {Component} from "@angular/core";
@Component({
  selector:'contact-list',
  template:`
      <h1>Contact-List</h1>
      <contact-a></contact-a>
      <contact-b></contact-b>
`
})
export class ContactListComponent{
  constructor(){}
}

結(jié)果將輸出:
Contact-List
ContactA:0.4500488165839276
ContactB:0.5389674473022938

上述的結(jié)果說明蝗拿,每個(gè)子組件都創(chuàng)建了自己獨(dú)立的注入器,也就是說通過依賴注入的Random服務(wù)都是獨(dú)立的蒿涎,如果把注入器提升到父組件中哀托,則結(jié)果將會(huì)不一樣。

//子組件A
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
  selector:'contact-a',
  //providers:[Random],
  template:`<div>ContactA:{{random.num}}</div>`
})
export class contactAComponent{
  random:Random;
  constructor(r:Random){
    this.random=r;
  }
}
//子組件B
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
  selector:'contact-b',
  //providers:[Random],
  template:`<div>ContactB:{{random.num}}</div>`
})
export class contactBComponent{
  random:Random;
  constructor(r:Random){
    this.random=r;
  }
}
//父組件
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
  selector:'contact-list',
  providers:[Random],
  template:`
      <h1>Contact-List</h1>
      <contact-a></contact-a>
      <contact-b></contact-b>
`
})
export class ContactListComponent{
  constructor(){}
}

此時(shí)劳秋,結(jié)果變?yōu)?br> Contact-List
ContactA:0.6257492668005642
ContactB:0.6257492668005642

上述的輸出結(jié)果說明了子組件繼承了父組件的注入器仓手,所以子組件使用了相同的Random實(shí)例胖齐,輸出了相同的結(jié)果。

那么俗或,該如何選擇在根組件還是在子組件中注入服務(wù)呢市怎?
這取決于想讓注入的依賴服務(wù)具有局限性還是全局性,由于每個(gè)注入器總是將它提供的服務(wù)維持單例辛慰,因此区匠,如果不需要針對(duì)每個(gè)組件都提供獨(dú)立的服務(wù)單例,就可以在根組件中注入帅腌,整個(gè)組件樹共享根注入器提供的服務(wù)實(shí)例驰弄;如果需要針對(duì)每個(gè)組件提供不同的服務(wù)實(shí)例,就應(yīng)該在格子組件中配置providers元數(shù)據(jù)來注入服務(wù)速客。

Angular如何查找到合適的服務(wù)實(shí)例呢戚篙?
在組件的構(gòu)造函數(shù)視圖注入某個(gè)服務(wù)的時(shí)候,Angular會(huì)先從當(dāng)前組件的注入器中查找溺职,找不到就繼續(xù)往父組件的注入器查找岔擂,直到根組件注入器,最后到應(yīng)用根注入器浪耘,此時(shí)找不到的話就會(huì)報(bào)錯(cuò)乱灵。

六、使用工廠函數(shù)

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';


import {AppComponent} from './app.component';
import {Product1Component} from './product1/product1.component';
import {ProductService} from './shared/product.service';
import {Product2Component} from './product2/product2.component';
import {LoggerService} from './shared/logger.service';
import {AnotherProductService} from './shared/another-product.service';


@NgModule({
  declarations: [
    AppComponent,
    Product1Component,
    Product2Component
  ],
  imports: [
    BrowserModule
  ],
  providers: [
    {
      provide: ProductService,
      useFactory: () => {
        let logger = new LoggerService();
        let dev = Math.random() > 0.5;
        if (dev) {
          return new ProductService(logger);
        } else {
          return new AnotherProductService(logger);
        }
      }
    }
    , LoggerService],
  bootstrap: [AppComponent]
})
export class AppModule {
}

這樣我們根據(jù)生成隨機(jī)數(shù)的不同而調(diào)用不同的服務(wù)七冲,注意Product1Component和Product2Component兩個(gè)組件使用的服務(wù)永遠(yuǎn)是相同的痛倚,這意味著工廠方法創(chuàng)建的對(duì)象是一個(gè)單例對(duì)像,工廠方法只在創(chuàng)建第一個(gè)需要被注入的對(duì)象時(shí)調(diào)用一次澜躺,然后在整個(gè)應(yīng)用中所有被注入的ProductService的實(shí)例都是同一個(gè)對(duì)象蝉稳。

現(xiàn)在有兩個(gè)問題,一是我們手動(dòng)實(shí)例化ProductService掘鄙,這樣會(huì)造成我們的工廠方法會(huì)跟LoggerService緊密的耦合在一起耘戚,而實(shí)際上我們是有聲明LoggerService這個(gè)提供器的,接下來我們將在工廠方法里使用使用LoggerService提供器操漠。

@NgModule({
 declarations: [
   AppComponent,
   Product1Component,
   Product2Component
 ],
 imports: [
   BrowserModule
 ],
 providers: [
   {
     provide: ProductService,
     useFactory: (logger: LoggerService) => {
       let dev = Math.random() > 0.5;
       if (dev) {
         return new ProductService(logger);
       } else {
         return new AnotherProductService(logger);
       }
     },
     deps: [LoggerService]
   }
   , LoggerService],
 bootstrap: [AppComponent]
})
export class AppModule {
}

使用deps聲明工廠方法需要的參數(shù)毕莱。

第二個(gè)問題,在我們的例子中實(shí)例話哪個(gè)對(duì)象是由一個(gè)隨機(jī)數(shù)決定的颅夺,在真實(shí)情況中我們可能會(huì)通過某個(gè)變量來控制朋截。這時(shí)我們聲明一個(gè)新的提供器

@NgModule({
  declarations: [
    AppComponent,
    Product1Component,
    Product2Component
  ],
  imports: [
    BrowserModule
  ],
  providers: [
    {
      provide: ProductService,
      useFactory: (logger: LoggerService, isDev) => {
        if (isDev) {
          return new ProductService(logger);
        } else {
          return new AnotherProductService(logger);
        }
      },
      deps: [LoggerService, 'IS_DEV_ENV']
    }
    , LoggerService,
    {
      provide: 'IS_DEV_ENV', useValue: false
    }],
  bootstrap: [AppComponent]
})
export class AppModule {
}

也可以使用值對(duì)象

@NgModule({
  declarations: [
    AppComponent,
    Product1Component,
    Product2Component
  ],
  imports: [
    BrowserModule
  ],
  providers: [
    {
      provide: ProductService,
      useFactory: (logger: LoggerService, appConfig) => {
        if (appConfig.isDev) {
          return new ProductService(logger);
        } else {
          return new AnotherProductService(logger);
        }
      },
      deps: [LoggerService, 'APP_CONFIG']
    }
    , LoggerService,
    {
      provide: 'APP_CONFIG', useValue: {isDev:false}
    }],
  bootstrap: [AppComponent]
})
export class AppModule {
}

七、在服務(wù)中注入服務(wù)

//logger.service.ts
import {Injectable} from "@angular/core";
@Injectable()
export class LoggerService{
  log(message:string){
    console.log(message);
  }
}


//contact.service.ts
import {Injectable} from "@angular/core";
import {LoggerService} from "./logger.service";
@Injectable()//添加裝飾器@Injectable()
export class ContactService{
  //構(gòu)造函數(shù)中注入所依賴服務(wù)
  constructor(private _logger:LoggerService){}
  getCollections(){
    this._logger.log('獲取聯(lián)系人...')
  }
}


//在組件的providers元數(shù)據(jù)中注冊(cè)服務(wù)
providers:[LoggerService,ContactService]
  • 只有聲明了@Injectable()這個(gè)裝飾器的服務(wù)才可以被注入其他服務(wù)吧黄。
  • 組件中沒有@Injectable()也可在構(gòu)造函數(shù)中注入服務(wù)的原因是組件中@Component是@Injectable()的子類部服。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市拗慨,隨后出現(xiàn)的幾起案子廓八,更是在濱河造成了極大的恐慌奉芦,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剧蹂,死亡現(xiàn)場(chǎng)離奇詭異声功,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)宠叼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門先巴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人冒冬,你說我怎么就攤上這事伸蚯。” “怎么了简烤?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵剂邮,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我横侦,道長(zhǎng)挥萌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任枉侧,我火速辦了婚禮引瀑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘棵逊。我一直安慰自己,他們只是感情好银酗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布辆影。 她就那樣靜靜地躺著,像睡著了一般黍特。 火紅的嫁衣襯著肌膚如雪蛙讥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天灭衷,我揣著相機(jī)與錄音次慢,去河邊找鬼。 笑死翔曲,一個(gè)胖子當(dāng)著我的面吹牛迫像,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瞳遍,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼闻妓,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了掠械?” 一聲冷哼從身側(cè)響起由缆,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤注祖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后均唉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體是晨,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年舔箭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了罩缴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡限嫌,死狀恐怖靴庆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情怒医,我是刑警寧澤炉抒,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站稚叹,受9級(jí)特大地震影響焰薄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜扒袖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一塞茅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧季率,春花似錦野瘦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至泞遗,卻和暖如春惰许,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背史辙。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工汹买, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人聊倔。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓晦毙,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親耙蔑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子结序,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)纵潦,斷路器徐鹤,智...
    卡卡羅2017閱讀 134,628評(píng)論 18 139
  • 一垃环、什么是依賴注入 控制反轉(zhuǎn)(IoC) 控制反轉(zhuǎn)的概念最早在2004年由Martin Fowler提出,是針對(duì)面向...
    Keriy閱讀 3,172評(píng)論 0 8
  • 版本:Angular 5.0.0-alpha 依賴注入是重要的應(yīng)用設(shè)計(jì)模式返敬。它使用得非常廣泛遂庄,以至于幾乎每個(gè)人都稱...
    soojade閱讀 2,986評(píng)論 0 3
  • 最近剛剛看完了閻真的《活著之上》涛目,這本僅差一個(gè)名次遺憾未拿到矛盾文學(xué)獎(jiǎng)的心靈讀本,用理想與現(xiàn)實(shí)的鮮明對(duì)照凛澎,拷問著學(xué)...
    憂郁的神經(jīng)閱讀 337評(píng)論 0 0
  • 來北京一年多了霹肝,總想寫點(diǎn)什么,想說點(diǎn)什么塑煎。此時(shí)在地鐵上沫换,心是累的,身體是疲倦的最铁。 一年前帶著夢(mèng)想揣著滿身的激情來到...
    我不告訴你明天的樣子閱讀 167評(píng)論 0 0