1. 程式人生 > >深入理解Angular依賴注入

深入理解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方法(也就是服務定義函式)

因為constantsvalues總是返回一個固定值,它們不是通過injector呼叫的,因此你不能給他們注入任何東西。

Configuring Providers

你可能會有疑問,既然factoryvalue等方法如此方面,為什麼有人還用provider的provide方法建立一個完整的provider。答案是:providers允許大量的配置。我們之前提到當你通過provider建立服務(或者通過Angular提供的任何快捷方式),你建立了該服務的建構函式。我沒有提到的是這些服務可以被注入到你的應用的config中,從而你可以和他們互動。

首先,Angular執行你的應用程式在兩個階段--configrun執行階段。配置階段,上面我們看到的,你可以設定任何需要的provider。這也是指令、控制器、過濾器設定的地方。執行階段,你可能猜測,是Angular編譯DOM然後開始執行應用的地方。

你可以通過myMod.configmyMod.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

filterdirective工作的方式和 controller一樣;filter使用一個名為$filter的服務,它的provider名為$filterProviderdirective使用一個名為$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公眾號
AngularJS公眾號