AngularJS中的依賴注入

理解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。下面是相應的文檔:

其中,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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章