深入理解Angular依賴注入
AngularJS依賴注入非常有用,並且是建立可測試元件的關鍵。本文解釋了AngularJS依賴注入如何工作的。
The Provider ($provide
)
$provide
負責告訴Angular如何建立新的可注入的事物,即服務。服務是被providers定義的,即當你用$provide
建立的。通過$provide
服務來定義provider,你可以通過在應用的配置函式中注入$provide
來持有該服務。如下:
myMod.config(function($provide) {
$provide.provider('greeting', function() {
this.$get = function() {
return function(name) {
alert("Hello, " + name);
};
};
});
});
這裡我們定義了一個名為greeting
的provider;我們可以在任何函式中(例如controllers)注入一個名為greeting
的變數,Angular會呼叫這個provider 的$get方法來返回該服務的一個例項。在上面的例子中,注入了一個函式,該函式有一個引數name
,彈出了一個和name
有關的訊息框。我們可以像下面這樣使用它:
myMod.controller('MainController' , function($scope, greeting) {
$scope.onClick = function() {
greeting('Ford Prefect');
};
});
現在有一個訣竅:factory
, service
, value
都是定義provider的快捷方式–即它們提供了不同的方式來定義provider而不用使用很長的定義。例如,你可以像下面那樣定義一個和上例一樣的provider
myMod.config(function($provide) {
$provide.factory('greeting', function() {
return function(name) {
alert("Hello, " + name);
};
});
});
理解這個很重要,因此我換一種說法:在底層,不論我們寫了上面的哪一種方法,AngularJS呼叫了同樣的程式碼。字面上的意思是:上面的兩種版本沒有任何差別。value
也是同樣工作的–如果我們在$get
方法(也就是factory
方法) 中返回的一直相同,我們可以通過使用value
方法來寫更少的程式碼。例如,因為在greeting
中,我們一直返回了同樣的函式,我們可以通過value
來這樣定義它:
myMod.config(function($provide) {
$provide.value('greeting', function(name) {
alert("Hello, " + name);
});
});
It'
再次強調,這種方法和以上兩種100%一樣,我們使用它定義方法僅僅是為了精簡程式碼。
你可能注意到了在myMod.config(function($provide) { ... })
中我是用了匿名函式。既然通過上面的方法定義provider很常用,AngularJS直接在模組物件上暴露了$provide
方法,來更大程度上精簡程式碼。
var myMod = angular.module('myModule', []);
myMod.provider("greeting", ...);
myMod.factory("greeting", ...);
myMod.service("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.service('greeting', function() {
return function(name) {
alert("Hello, " + name);
};
});
myMod.value('greeting', function(name) {
alert("Hello, " + name);
});
The Injector ($injector
)
injector 負責通過使用用$provide
提供的程式碼建立服務例項。一旦你建立含有注入引數的函式,injector就開始工作了。每個AngularJS應用在啟動的時候會建立一個$injector
;你可以在任何可以注入的函式中用$injector
注入它從而得到其例項(是的,$injector
知道如果注入它自己)。
一旦你有了$injector
,你可以呼叫它通過一個已定義的服務的名稱取到該服務的例項。例如:
var greeting = $injector.get('greeting');
greeting('Ford Prefect');
injector也負責注入服務到函式中。例如:你可以使用injector的invoke
方法將服務注入到函式中。
var myFunction = function(greeting) {
greeting('Ford Prefect');
};
$injector.invoke(myFunction);
應該注意到injector
只會建立一次某個服務的示例。然後,它通過服務的名稱將provider返回值快取起來,下次你請求該服務,你會得到同一個物件。
因此,顯而易見地,你可以通過呼叫$injector.invoke
注入服務到任何函式中。包括
- 控制器定義函式
- 指令定義函式
- 過濾器定義函式
- provider的
$get
方法(也就是服務定義函式)
因為constants
和values
總是返回一個固定值,它們不是通過injector呼叫的,因此你不能給他們注入任何東西。
Configuring Providers
你可能會有疑問,既然factory
、value
等方法如此方面,為什麼有人還用provider的provide
方法建立一個完整的provider。答案是:providers允許大量的配置。我們之前提到當你通過provider建立服務(或者通過Angular提供的任何快捷方式),你建立了該服務的建構函式。我沒有提到的是這些服務可以被注入到你的應用的config中,從而你可以和他們互動。
首先,Angular執行你的應用程式在兩個階段--config
和run
執行階段。配置階段,上面我們看到的,你可以設定任何需要的provider。這也是指令、控制器、過濾器設定的地方。執行階段,你可能猜測,是Angular編譯DOM然後開始執行應用的地方。
你可以通過myMod.config
和myMod.run
方法在這兩個階段中新增額外的程式碼執行。正如我們在第一個章節中看到的,這些函式是可以被注入的--在第一個例子中我們注入了內建的$provide服務。然而,值得注意的是,在配置階段,只有provider可以被注入($provide
和 $injector
例外)。
例如下面的例子中,是錯誤的:
myMod.config(function(greeting) {
// 不會工作 -- greeting是某個服務的例項
// 只有服務的provider才可以被注入到config塊中
});
你能獲取到的是你建立的服務的provider,如下:
myMod.config(function(greetingProvider) {
// a-ok!
});
有一個很重要的例外:constant
,既然它們不能夠改變,被允許注入到配置塊中(這是它們和value
的差別)。可以通過它們的名字獲取到(不用加Provider
字首)
當你給你的服務定義了一個provider,這個provider會被命名為serviceProvider
,service即服務的名稱。我們可以使用這個來做一些更復雜的事情。
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
;我們可以在配置塊中獲取到provider,呼叫該函式來定製服務。當我們最終執行我們的應用的時候,我們可以得到greeting
服務,呼叫它從而發現我們的定製起作用了。
Controllers ($controller)
你可以注入事物到控制器中,但是你不能把控制器注入到其他事物中。這是因為控制器不是通過provider
建立的,而是通過一個名為$controller
的Angular內建服務,該服務負責建立控制器。當你呼叫了myMod.controller(...)
,你獲取了該服務的provider,類似於上個章節所描述的。
例如,我們定義了一個控制器:
myMod.controller('MainController', function($scope) {
// ...
});
你實際上正在做這樣的操作:
myMod.config(function($controllerProvider) {
$controllerProvider.register('MainController', function($scope) {
// ...
});
});
隨後,當Angular建立控制器的例項的時候,它使用了$controller
服務(它反過來會使用$injector
來呼叫你的控制器,從而得到其依賴)。
Filters and Directives
filter
和directive
工作的方式和 controller
一樣;filter
使用一個名為$filter
的服務,它的provider名為$filterProvider
;directive
使用一個名為$compile
的服務,它的provider名為$compileProvider
。一些連結如下:
總結
總結一下,任何被$injector.invoke
呼叫的函式都可以被注入。包含但不侷限於以下:
- controller
- directive
- factory
- filter
- provider $get (當定義provider為物件時)
- provider function (當定義provider為建構函式時)
- service
provider建立了新的可以注入到其他事物中的服務。包括:
- constant
- factory
- provider
- service
- value
也就是說,內建服務(例如$controller
和$filter
)可以被注入,你可以通過這些服務來建立新的控制器和過濾器(即使你沒有定義它們,它們也是能夠被注入的)。
除了這個,任何injector觸發的函式可以被注入provider提供的服務--這是沒有限制的(除了配置和執行處的差異)。
可以關注AngularJS公眾號