理解AngularJS中的依賴注入
AngularJS中的依賴注入非常的有用,它同時也是我們能夠輕鬆對組件進行測試的關鍵所在。在本文中我們將會解釋AngularJS依賴注入系統是如何運行的。
Provider服務($provide)
$provide服務負責告訴Angular如何創造一個新的可注入的東西:即服務(service)。服務會被叫做provider的東西來定義,你可以使用$provide來創建一個provider。你需要使用$provide中的provider方法來定義一個provider,同時你也可以通過要求改服務被注入到一個應用的config函數中來獲得$provide服務。下面是一個例子:
app.config(function($provide) { $provide.provider('greeting', function() {this.$get = function() { return function(name) {alert("Hello, " + name); };}; });});
在上面的例子中我們爲一個服務定義了一個叫做greeting的新provider;我麼可以把一個叫做greeting的變量注入到任何可注入的函數中(例如控制器,在後面會講到)然後Angular就會調用這個provider的$get函數來返回這個服務的一個實例。在上面的例子中,被注入的是一個函數,它接受一個叫做name的參數並且根據這個參數alert一條信息。我們可以像下面這樣使用它:
app.controller('MainController', function($scope, greeting) { $scope.onClick = function() {greeting('Ford Prefect'); };});
現在有趣的事情來了。factory,service以及value全部都是用來定義一個providr的簡寫,它們提供了一種方式來定義一個provider而無需輸入所有的複雜的代碼。例如,你可以用下面的代碼定義一個和前面完全相同的provider:
app.config(function($provide) { $provide.factory('greeting', function() {return function(name) { alert("Hello, " + name);}; });});
這非常的重要,所以一定要記住:在幕後,AngularJS實際上是在調用前面出現的代碼(就是$provide.provider的版本)。從字面上來說,這兩種方法基本上沒有差別。value方法也一樣 – 如果我們需要從$get函數(也就是我們的factory函數)返回的值永遠相同的話,我們可以使用value方法來編寫更少的代碼。例如如果我們的greeting服務總是返回相同的函數,我們可以使用value來進行定義:
app.config(function($provide) { $provide.value('greeting', function(name) {alert("Hello, " + name); });});
再一次申明,以上兩種方式的效果完全一樣 – 只不過是代碼的量不同而已。
現在你可能已經注意到了我們使用的煩人的
app.config(function($provide){...})
代碼了。由於定義一個新的provider是如此的常用,AngularJS在模塊對象上直接暴露了provider方法,以此來減少代碼的輸入量:
var myMod = angular.module('myModule', []);myMod.provider("greeting", ...);myMod.factory("greeting", ...);myMod.value("greeting", ...);
上面的代碼和前面app.config(...)
這樣囉嗦的寫法完全相同。
除了上面提到的可以注入的東西之外,還有一個constant方法。基本上,它和value的用法一致。我們會在後面來討論兩者的不同點。
爲了鞏固前面的學習成果,下面所有的代碼所做的都是同一件事情:
myMod.provider('greeting', function() { this.$get = function() {return function(name) { alert("Hello, " + name);}; };});myMod.factory('greeting', function() { return function(name) {alert("Hello, " + name); };});myMod.value('greeting', function(name) { alert("Hello, " + name);});
注入器($injector)
注入器負責從我們通過$provide創建的服務中創建注入的實例。只要你編寫了一個帶有可注入性的參數,你都能看到注入器是如何運行的。每一個AngularJS應用都有唯一一個$injector,當應用啓動的時候它被創造出來,你可以通過將$injector注入到任何可注入函數中來得到它($injector知道如何注入它自己!)。
一旦你擁有了$injector,你可以動過調用get函數來獲得任何一個已經被定義過的服務的實例。例如:
var greeting = $injector.get('greeting');greeting('Ford Prefect');
注入器同樣也負責將服務注入到函數中;例如,你可以魔法般的將服務注入到任何函數中,只要你使用了注入器的invoke方法:
var myFunction = function(greeting) { greeting('Ford Prefect');};$injector.invoke(myFunction);
如果注入器只是創建一個服務的實例一次的話,那麼它也沒什麼了不起的。它的厲害之處在於,他能夠通過服務名稱緩存從一個provider中返回的任何東西,當你下一次再使用這個服務時,你將會得到同一個對象。
因此,你可以通過調用$injector.invike將服務注入到任何函數中也是合情合理的了。包括:
控制器定義函數
指令定義函數
過濾器定義函數
provider中的$get方法(也就是factory函數)
由於constant和value總是返回一個靜態值,它們不會通過注入器被調用,因此你不能在其中注入任何東西。
配置provider
你可能會感到困惑:既然factorry和value能夠節省那麼多的代碼,爲什麼還有人要使用provider。答案是provider允許我們進行一些配置。在前面我們已經提到過當你通過provider(或者其他簡寫方法)創建一個服務時,你實際上創建了一個新的provider,它將定義你的服務如何被創建。我們沒有提到的是,這些provider可以被注入到config函數中,你可以和它們進行一些交互。
首先,AngularJS分兩個階段運行你的應用 – config階段和run階段。config階段是你設置任何的provider的階段。它也是你設置任何的指令,控制器,過濾器以及其它東西的階段。在run階段,AngularJS會編譯你的DOM並啓動你的應用。
你可以在myMod.config和myMod.run中添加任何代碼 – 這兩個函數分別在兩個階段運行。正如我們看到的,這些函數都是可以被注入的 – 我們在第一個例子中注入了內建的$provide函數。然而,值得注意的是在config階段,只有provider能被注入(只有兩個例外是$provide和$injector)。
例如,下面的代碼就是錯誤的寫法:
myMod.config(function(greeting) { //不會運行 -- greeting是一個服務的實例 //只有服務的provider能被注入到config中});
但是你可以通過下面的方法注入provider:
myMod.config(function(greetingProvider) { // 這下好了!});
有一個例外:constant,由於它們不能被改變,因此它不能被注入到config中(這就是它和value之間的不同之處)。它們只能通過名字被獲取。
無論何時你爲一個服務定義了一個provider,這個provider的名字都是serviceProvider。在這裏service是服務的名字。現在我們可以使用provider的力量來做一些更復雜的事情了!
myMod.provider('greeting', function() { var text = 'Hello, '; this.setText = function(value) { text = value; }; this.$get = function() { return function(name) { alert(text + name); }; };});myMod.config(function(greetingProvider) { greetingProvider.setText("Howdy there, ");});myMod.run(function(greeting) { greeting('Ford Prefect');});
現在我們在provider中擁有了一個叫做setText的函數,我們可以使用它來自定義我們alert的內容;我們可以再config中訪問這個provider,調用setText方法並自定義我們的service。當我們最終運行我們的應用時,我們可以獲取greeting服務,然後你會看到我們自定義的行爲起作用了。
控制器($controller)
控制器函數是可以被注入的,但是控制器本身是不能被注入到任何東西里面去的。這是因爲控制器不是通過provider創建的。然而,有一個內建的AngularJS服務叫做1$controller,它負責設置你的控制器。當你調用myMod.controller(…)時,你實際上是訪問了這個服務的provider,就像上面的例子一樣。
例如,當你像下面一樣定義了一個控制器時:
myMod.controller('MainController', function($scope) { // ...});
你實際上做的是下面這件事:
myMod.config(function($controllerProvider) { $controllerProvider.register('MainController', function($scope) {// ... });});
當Angular需要創建一個你的控制的實例時,它會使用$controller服務(它反過來會使用$injector來調用你的控制器以便它能夠被注入依賴項)。
過濾器和指令
filter和directive和controller的運行方式相同;filter會使用一個叫做$filter的服務以及它的provider $filterProvider,而directive使用一個叫做$compile的服務以及它的provider $compileProvidr。下面是相應的文檔:
$filterProvider: http://docs.angularjs.org/api/ng.$filterProvider
$compileProvider: http://docs.angularjs.org/api/ng.$compileProvider
其中,myMod.filter和myMod.directive分別是這些服務的簡寫。
總結
總結一下,任何能夠被$injector.invoke調用的函數都是能被注入的。包括,但不限於下面的這些:
控制器
指令
factory
過濾器
provider中的$get函數
provider函數
服務
provider創建的新服務都可以用來注入。包括:
constant
factory
provider
service
value
另外,內建的服務$controller和$filter也可以被注入,同時你也可以使用這些服務來獲得新的過濾器和控制器。
本文譯自Understanding Dependency Injection,原文地址https://github.com/angular/angular.js/wiki/Understanding-Dependency-Injection