摘要:前言依賴注入是的核心概念之一。會(huì)幫我們管理并且維護(hù)這些依賴關(guān)系。在組件當(dāng)中我們沒(méi)有看到任何操作符,但是程序啟動(dòng)后我們可以看到控制臺(tái)打印了。
前言
依賴注入是Angular的核心概念之一。通過(guò)依賴注入,我們可以將復(fù)雜、繁瑣的對(duì)象管理工作交給Angular,將我們的工作重心更好的放在業(yè)務(wù)上。
依賴注入本身是后端編碼的概念,熟悉Spring框架的對(duì)其應(yīng)該不陌生,Angular1首次將依賴注入引入前端開發(fā),Angular2繼續(xù)將其發(fā)揚(yáng)光大,同時(shí)又很好的解決了Angular1中依賴注入所遺留的問(wèn)題和瓶頸。
那么什么是依賴注入呢?我覺(jué)得可以分為兩個(gè)方面去解讀
面向?qū)ο缶幊?,我們以類為單位組織我們的代碼。舉個(gè)簡(jiǎn)單的例子,例如某一款汽車,有引擎、輪胎、車門等配置,抽象成代碼就是這樣的
class Car { constructor() { this.engine = new Engine(); this.tires = new Tires(); this.doors = new Doors(); } }
在構(gòu)造汽車的過(guò)程中,我們安裝引擎、輪胎和車門等配置,這樣就可以造出一輛汽車了。但是現(xiàn)在我們還想造同一款車,但是想換一種引擎,怎么辦?很明顯,上面的Car是整個(gè)封閉的,如果想換一個(gè)引擎,我們就得重新造一款車
class OtherCar { constructor() { this.engine = new OtherEngine(); this.tires = new Tires(); this.doors = new Doors(); } }
相信大家已經(jīng)發(fā)現(xiàn)上面代碼的問(wèn)題了,耦合性太強(qiáng),無(wú)法定制我們的引擎、輪胎和車門,要想定制,就得從頭來(lái)過(guò)。如果我們的所有的引擎都符合某一個(gè)標(biāo)準(zhǔn)尺寸,然后在車?yán)镱A(yù)留出這個(gè)空間,那么我們不就可以隨意更換引擎了么?同理輪胎和車門,抽象成代碼就是這樣的
class Car { constructor(engine, tires, doors) { this.engine = engine; this.tires = tires; this.doors = doors; } }
通過(guò)組裝的方式造車,預(yù)留配置的標(biāo)準(zhǔn)空間,同一款車我們可以隨意使用各種配置
var car = new Car( new Engine(), new Tires(), new Doors() );
var car = new Car( new MockEngine(), new MockTires(), new MockDoors() );
從測(cè)試的角度來(lái)說(shuō),這樣的代碼也是方便測(cè)試的。上面的注入方式就是構(gòu)造器注入,通過(guò)這樣的一種模式可以使我們的代碼更加健壯同時(shí)也是易于測(cè)試的。
但是上面注入的實(shí)例都是我們手動(dòng)去new的,當(dāng)應(yīng)用越來(lái)越大的時(shí)候,我們的依賴會(huì)更復(fù)雜,試著想一下某個(gè)類依賴于十幾個(gè)類,而這些類之間又相互依賴,管理這些依賴關(guān)系就是件讓人頭疼的事情了。
Angular會(huì)幫我們管理并且維護(hù)這些依賴關(guān)系。
Angular1中我們可以使用service注入服務(wù),就像這樣
angular.module("app", []) .controller("MyCtrl", function ($scope, comService) { comService.handle(); }) .service("comService", function () { this.handle = function () { //todo } });
但是Angular1的依賴注入有幾個(gè)問(wèn)題
所有的服務(wù)全部都是單例的
var id = 1; angular.module("app", []) .service("comService", function () { this._id = id++; this.getId = function () { return this._id; } }) .controller("ACtrl", function ($scope, comService) { console.log(comService.getId()); // 1 }) .controller("BCtrl", function ($scope, comService) { console.log(comService.getId()); // 1 });
服務(wù)是通過(guò)名稱來(lái)區(qū)分的,很容易造成沖突,后者會(huì)直接覆蓋前者
angular.module("app", []) .service("comService", function () { this.name = "company service 1"; }) .service("comService", function () { this.name = "company service 2"; }) .controller("ACtrl", function ($scope, comService) { console.log(comService.name); // company service 2 });
依賴注入功能內(nèi)嵌在Angular1中,無(wú)法剝離出來(lái)多帶帶使用
Angular2中的依賴注入 組件注入服務(wù)例如有一個(gè)日志服務(wù)logger.service.ts
export default class LoggerService { log(str) { console.log(`Log: ${str}`); } }
然后入口組件app.ts當(dāng)中使用這個(gè)服務(wù)
import {Component} from "angular2/core"; import LoggerService from "./logger.service"; @Component({ selector: "my-app", template: "App Component
", providers:[LoggerService] }) export class AppComponent { loggerService:LoggerService; constructor(loggerService:LoggerService) { this.loggerService = loggerService; } ngOnInit(){ this.loggerService.log("component init"); } }
首先我們需要在組件的providers配置中引入這個(gè)服務(wù),這點(diǎn)很重要,在Angular2的任何組件(指令等等)當(dāng)中想要使用我們自定義的服務(wù)或者其它功能必須先作出聲明。
在App組件當(dāng)中我們沒(méi)有看到任何new操作符,但是程序啟動(dòng)后我們可以看到控制臺(tái)打印了Log: component init。Angular2幫我們實(shí)例化了LoggerService并注入到了loggerService屬性當(dāng)中。
上面的代碼還可以簡(jiǎn)寫成這樣
@Component({ selector: "my-app", template: "App Component
", providers:[LoggerService] }) export class AppComponent { constructor(private loggerService:LoggerService) {} ngOnInit(){ this.loggerService.log("component init"); } }
loggerService:LoggerService,后面指定的類型必不可少,這是注入的關(guān)鍵
在Angular2組件當(dāng)中使用依賴注入可以簡(jiǎn)單的分為兩步
組件當(dāng)中作出聲明
組件構(gòu)造函數(shù)當(dāng)中注入
子組件注入服務(wù)新建一個(gè)uuid.ts的服務(wù),可以生成一個(gè)唯一的ID
var id = 1; export default class UuidService { id:number; constructor() { this.id = id++; } getId() { return this.id; } }
入口組件app.ts
import {Component} from "angular2/core"; import UuidService from "./uuid.service"; import ChildComponent from "./child"; @Component({ selector: "my-app", template: "App Component
", providers:[UuidService], directives:[ChildComponent] }) export class AppComponent { constructor(private uuidService:UuidService) {} ngOnInit(){ console.log(this.uuidService.getId()); } }
新建一個(gè)子組件child.ts
import {Component} from "angular2/core"; import UuidService from "./uuid.service"; @Component({ selector: "my-child", template: "Child Component
" }) export default class ChildComponent { constructor(private uuidService:UuidService) {} ngOnInit(){ console.log(this.uuidService.getId()) } }
在子組件當(dāng)中我們并沒(méi)有配置providers,為啥程序依然正常執(zhí)行呢?因?yàn)樽咏M件可以注入父組件聲明的服務(wù)。打開控制臺(tái)看到輸出了兩個(gè)1,說(shuō)明父子組件注入的是同一個(gè)實(shí)例,這并不符合uuid的功能,怎么辦?
我們把子組件當(dāng)中的providers聲明加上
import {Component} from "angular2/core"; import UuidService from "./uuid.service"; @Component({ selector: "my-child", template: "Child Component
", providers:[UuidService] }) export default class ChildComponent { constructor(private uuidService:UuidService) {} ngOnInit(){ console.log(this.uuidService.getId()) } }
打開控制臺(tái),發(fā)現(xiàn)打印了1 2,這是為什么呢?Angular2當(dāng)中每個(gè)組件都有自己的依賴注入管理,依賴注入的時(shí)候會(huì)先在當(dāng)前組件上尋找服務(wù)實(shí)例,如果找不到就會(huì)使用父組件上依賴注入的實(shí)例,如果還找不到,就會(huì)拋出異常。
組件是一個(gè)樹狀結(jié)構(gòu),我們也可以把依賴注入看成和組件平行的樹狀結(jié)構(gòu),每個(gè)組件都有自己的依賴管理,這樣就解決了Angular1當(dāng)中服務(wù)單例的的問(wèn)題。
有時(shí)候服務(wù)之間也會(huì)相互依賴,例如上面的例子當(dāng)中LoggerService依賴另一個(gè)FormatService
format.service.ts
export default class FormatService { format() { return "Log: "; } }
logger.service.ts
import FormatService from "./format.service"; export default class LoggerService { constructor(private formatService:FormatService) { } log(str) { console.log(`${this.formatService.format()}${str}`); } }
app.ts
import {Component} from "angular2/core"; import LoggerService from "./logger.service"; import FormatService from "./format.service"; @Component({ selector: "my-app", template: "App Component
", providers: [LoggerService, FormatService] }) export class AppComponent { constructor(private loggerService:LoggerService) { } ngOnInit() { this.loggerService.log("component init"); } }
服務(wù)依賴的服務(wù)也要在providers中作出聲明
打開控制臺(tái),發(fā)現(xiàn)拋出了異常,因?yàn)槲覀儧](méi)有告知Angular2,LoggerService依賴FormatService,所以注入失敗了。
通過(guò)給LoggerService添加@Injectable()裝飾器,告知Angular2本服務(wù)需要注入其它服務(wù)
logger.service.ts
import FormatService from "./format.service"; import {Injectable} from "angular2/core"; @Injectable() export default class LoggerService { constructor(private formatService:FormatService) { } log(str) { console.log(`${this.formatService.format()}${str}`); } }
這樣我們的程序又能正常工作了。細(xì)心的同學(xué)會(huì)發(fā)現(xiàn)我們的App組件也需要注入LoggerService服務(wù),為什么不需要添加@Injectable()裝飾器?
因?yàn)榻M件聲明已經(jīng)添加了@Component()裝飾器,所以無(wú)需再次添加其它聲明了。
循環(huán)依賴注入建議我們所有的服務(wù)都添加上@Injectable()
我們將上面的代碼改造成下面這樣
format.service.ts
import LoggerService from "./logger.service"; import {Injectable} from "angular2/core"; @Injectable() export default class FormatService { constructor(private loggerService:LoggerService){} format() { return "Log: "; } }
logger.service.ts
import FormatService from "./format.service"; import {Injectable} from "angular2/core"; @Injectable() export default class LoggerService { constructor(private formatService:FormatService) { } log(str) { console.log(`${this.formatService.format()}${str}`); } }
打開控制臺(tái)會(huì)發(fā)現(xiàn)拋出了異常,像這種兩個(gè)服務(wù)之間相互注入的情況就會(huì)產(chǎn)生循環(huán)依賴,我們要盡量避免這種情況的發(fā)生,保持每個(gè)服務(wù)的單一職責(zé)功能。
依賴注入核心Angular2的依賴注入主要由三個(gè)部分構(gòu)成
Injector - 暴露接口創(chuàng)建服務(wù)實(shí)例
Provider - 包含了當(dāng)前服務(wù)的信息和依賴信息
Dependency - 服務(wù)的依賴信息
通過(guò)Injector的功能,我們可以脫離Angular2組件來(lái)使用依賴注入,例如上面的Car例子,首先引入
import {Injector, Injectable} from "angular2/core";
創(chuàng)建我們的Engine等類和Car類
class Engine{} class Tires{} class Doors{} @Injectable() class Car{ constructor(private engine:Engine, private tires:Tires, private dorrs:Doors){} }
Car當(dāng)中需要注入別的類,不要忘了添加 @Injectable()
調(diào)用Injector的resolveAndCreate靜態(tài)方法創(chuàng)建注入器
var injector = Injector.resolveAndCreate([Engine, Tires, Doors, Car]);
要將所有相關(guān)的類添加到參數(shù)數(shù)組中,如果實(shí)例化了參數(shù)數(shù)組中不存在的類,就會(huì)拋出異常
調(diào)用get方法獲取Car類的實(shí)例
var car = injector.get(Car);
比較下面的例子
injector.get(Tires) === injector.get(Tires); //true car.engine === injecotr.get(Engine); //true
Token同一個(gè)注入器上獲取的實(shí)例都是單例的
我們知道Angular1當(dāng)中注入的識(shí)別是通過(guò)參數(shù)的字符名稱,例如
angular.module("app", []) .service("comService", function () { }) .controller("ACtrl", function (comService) { });
controller當(dāng)中使用的service名稱必須和注冊(cè)處保持一致,否則注入失敗。Angular2獲取實(shí)例則是通過(guò)Token
var injector = Injector.resolveAndCreate([Engine]);
這種方式實(shí)際上是簡(jiǎn)寫的,Angular2會(huì)幫我們封裝成下面的形式
var injecotr = Injector.resolveAndCreate([provide(Engine,{useClass:Engine})]);
provide是Angular2的核心方法之一,返回值是一個(gè)Provider實(shí)例。第一個(gè)參數(shù)就是Token,這里我們直接使用了類Engine作為Token,useClass表示通過(guò)實(shí)例化類的方式注入。
實(shí)際上Token可以換成別的類型,例如
var injector = Injector.resolveAndCreate([provide("engine", {useClass: Engine})]); var engine = injector.get("engine"); console.log(engine instanceof Engine); //true
useClass當(dāng)然了使用字符串這種方式容易被覆蓋
實(shí)例化類的方式注入,注入器會(huì)幫我們new實(shí)例,如果傳遞一個(gè)非類,typescript編譯都通不過(guò)
useValue直接注入這個(gè)值
var injector = Injector.resolveAndCreate([ provide(Engine, {useValue: "engine"}) ]); console.log(injector.get(Engine) === "engine"); //trueuseFactory
注入工廠方法的返回值
var injector = Injector.resolveAndCreate([provide(Engine, { useFactory: function () { return "engine" } })]); console.log(injector.get(Engine) === "engine");
factory方法當(dāng)中可以依賴別的服務(wù)
var injector = Injector.resolveAndCreate([EngineA, EngineB, provide(Engine, { useFactory: function (engineA, engineB) { if (true) { return engineA; } else { return engineB; } }, deps: [EngineA, EngineB] })]); console.log(injector.get(Engine) instanceof EngineA); //trueuseExisting
使用已存在的實(shí)例注入,這個(gè)容易跟useClass弄混,注意下面的輸出
var injector = Injector.resolveAndCreate([ EngineA, provide(EngineB, {useClass: EngineA}) ]); console.log(injector.get(EngineA) === injector.get(EngineB)); //false var injector = Injector.resolveAndCreate([ EngineA, provide(EngineB, {useExisting: EngineA}) ]); console.log(injector.get(EngineA) === injector.get(EngineB)); //truemulti
如果我們重復(fù)注冊(cè)同一個(gè)Token,后面的會(huì)覆蓋前面的,例如
var injector = Injector.resolveAndCreate([ provide("COM_ID", {useValue: 1}), provide("COM_ID", {useValue: 2}) ]); console.log(injector.get("COM_ID")); // 2
使用multi配置可以使相同的Token共存,注入的是一個(gè)數(shù)組
var injector = Injector.resolveAndCreate([ provide("COM_ID", { useValue: 1, multi: true }), provide("COM_ID", { useValue: 2, multi: true }) ]); console.log(injector.get("COM_ID")); // [1,2]
相同的Token,不能出現(xiàn)混合的情況,例如下面的寫法就會(huì)報(bào)錯(cuò)
var injector = Injector.resolveAndCreate([ provide("COM_ID", {useValue: 1, multi: true}), provide("COM_ID", {useValue: 2}) ]);子注入器
通過(guò)resolveAndCreateChild可以創(chuàng)建子注入器
var injector = Injector.resolveAndCreate([Engine, Tires, Doors, Car]); var childInjector = injector.resolveAndCreateChild([Engine, Car]); var grantInjector = childInjector.resolveAndCreateChild([Car]); grantInjector.get(Car) === childInjector.get(Car); //false grantInjector.get(Car) === injector.get(Car); //false grantInjector.get(Engine) === childInjector.get(Engine); //true childInjector.get(Engine) === injector.get(Engine); //false grantInjector.get(Tires) === childInjector.get(Tires); //true childInjector.get(Tires) === injector.get(Tires); //true
小結(jié)每個(gè)注入器都會(huì)有自己的依賴注入管理,它會(huì)先從本身查找服務(wù),如果找不到就會(huì)往父級(jí)注入器查找
自此Angular2解決了Angular1遺留的問(wèn)題
我們可以多帶帶使用依賴注入功能
Token防止重名覆蓋
樹狀的注入器各自管理自己的實(shí)例
原文
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/79236.html
摘要:前集回顧上一章里我們?cè)诶锿ㄟ^(guò)組合三個(gè)組件,并通過(guò)單向數(shù)據(jù)流的方式把她們驅(qū)動(dòng)起來(lái)。設(shè)計(jì)每章都會(huì)提一下,先設(shè)計(jì)使用場(chǎng)景這種方式,我們稱之為,不了解的朋友參考以手寫依賴注入。 前集回顧 上一章里我們?cè)贏ppComponent里通過(guò)組合InputItem、 CheckableItem、 Counter三個(gè)組件,并通過(guò)Unidirectional Data Flow(單向數(shù)據(jù)流)的方式把她們驅(qū)動(dòng)...
摘要:通過(guò)增加刪除元素改變布局的。譬如和控制元素顯示隱藏,或者改變?cè)匦袨榈?。譬如設(shè)計(jì)看過(guò)我之前介紹以手寫依賴注入的朋友應(yīng)該已經(jīng)對(duì)行為驅(qū)動(dòng)多少有些了解了。她有,并且包含了至少一個(gè)和一個(gè)標(biāo)簽。,將左邊的事件傳遞給了右邊的表達(dá)式通常就是事件處理函數(shù)。 前集回顧 在上一章里我們講了如何為angular2搭建開發(fā)環(huán)境(還沒(méi)搭起來(lái)的趕緊去看哦),并使之跑起來(lái)我們的第一個(gè)My First Angular...
摘要:依賴注入并不限于構(gòu)造函數(shù)作為經(jīng)驗(yàn),注入最適合必須的依賴關(guān)系,比如示例中的情況注入最適合可選依賴關(guān)系,比如緩存一個(gè)對(duì)象實(shí)例。 本文翻譯自 Symfony 作者 Fabien Potencier 的 《Dependency Injection in general and the implementation of a Dependency Injection Container in P...
摘要:上文書,創(chuàng)建對(duì)象需要先創(chuàng)建對(duì)象。創(chuàng)建對(duì)象的雜活是嵌入在中的。對(duì)象使用來(lái)管理依賴關(guān)系非常好,但不是必須的。很容易實(shí)現(xiàn),但手工維護(hù)各種亂七八糟的對(duì)象還是很麻煩。所有文章均已收錄至項(xiàng)目。 本文翻譯自 Symfony 作者 Fabien Potencier 的 《Dependency Injection in general and the implementation of a Depend...
摘要:代碼這就是控制反轉(zhuǎn)模式。是變量有默認(rèn)值則設(shè)置默認(rèn)值是一個(gè)類,遞歸解析有默認(rèn)值則返回默認(rèn)值從容器中取得以上代碼的原理參考官方文檔反射,具有完整的反射,添加了對(duì)類接口函數(shù)方法和擴(kuò)展進(jìn)行反向工程的能力。 PHP程序員如何理解依賴注入容器(dependency injection container) 背景知識(shí) 傳統(tǒng)的思路是應(yīng)用程序用到一個(gè)Foo類,就會(huì)創(chuàng)建Foo類并調(diào)用Foo類的方法,假如這...
閱讀 4306·2021-09-26 10:11
閱讀 2700·2021-07-28 00:37
閱讀 3244·2019-08-29 15:29
閱讀 1207·2019-08-29 15:23
閱讀 3154·2019-08-26 18:37
閱讀 2492·2019-08-26 10:37
閱讀 623·2019-08-23 17:04
閱讀 2372·2019-08-23 13:44