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