angular學習(六)—— 依賴注入

來源地址:http://blog.csdn.net/lastsweetop/article/details/53409171

依賴注入

依賴注入(DI)是一種處理組件如何獲得依賴的軟件設計模式,在Angular中,injector子系統負責創建組件,解決組件的依賴,並將它們提供給其他組件。

使用依賴注入

在Angular中DI是無處不在的,你可以用它來定義一個組件,也可以提供module實現run和config代碼塊。 
- 像services, directives, filters 和 animations這樣的組件都由一個工廠方法或者構造函數定義,在此service和value組件可以作爲依賴注入到這些組件當中。 
- Controllers通過一個構造函數定義,一樣service和value組件可以作爲依賴注入到組件當中,但他們也有着特殊的依賴關係,我們在angular學習(三)—— Controller 中有講到。 
- Module的run方法接受一個函數,在函數的參數中作爲依賴可以注入service,value和constant組件,但是不能注入providers。 
- Module的config方法接受一個函數,在函數的參數中作爲依賴可以注入provider和constant組件,但是不能注入service和value。

factory方法

directive, service, or filter都由一個工廠方法定義,該方法通過modules註冊。定義這個工廠方法推薦的寫法如下:

angular.module('myModule', [])
.factory('serviceId', ['depService', function(depService) {
  // ...
}])
.directive('directiveName', ['depService', function(depService) {
  // ...
}])
.filter('filterName', ['depService', function(depService) {
  // ...
}]);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

module方法

我們可以指定module在配置和運行時需要調用的config方法和run方法。這些函數就像上面的工廠方法一樣可以注入依賴。

angular.module('myModule', [])
.config(['depProvider', function(depProvider) {
  // ...
}])
.run(['depService', function(depService) {
  // ...
}]);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

controller

controller能夠提供讓application可以在模板定義聲明性標記的行爲(方法)。定義controller中使用依賴注入的推薦方法是用一個數組。

someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) {
  ...
  $scope.aMethod = function() {
    ...
  }
  ...
}]);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

和service不同的是,在同一個應用程序中可以有許多相同類型的控制器實例。 
此外,控制器可以使用一些額外的依賴關係:

  • $scope:控制器與DOM中的一個元素相關聯,因此提供了以元素相對應的訪問範圍。其他組件(如service)只能訪問$rootScope服務
  • resolves: 如果controller被實例化爲route的一部分,作爲route的一部分解析的任何值都可用於注入控制器.

依賴註解

Angular調用通過注入調用一些函數時(比如service的工廠方法和controller),爲了讓injector知道需要將哪些service注入到函數中,你需要對這些函數進行註解。這裏有三種註解方式: 
- 內聯數組註解,最好優先使用這種方式 
- 用 $inject屬性註解 
- 以函數的參數名字隱式註解,這種方式有一些警告。

內聯數組註解

這是angular程序註解組件的首選方法,本系列的文章都是這種方式。

omeModule.controller('MyController', ['$scope', 'greeter', function($scope, greeter) {
  // ...
}]);
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

內聯函數由所需依賴名字構成的字符串列表和函數本身構成。當使用這種註解方式時要注意,內聯數組中所需依賴名字字符串順序和函數中的參數順序要一致。

$inject屬性註解

使用$inject屬性一樣可以做到這一點,$inject屬性是一個由所需依賴名字構成字符串數組。

var MyController = function($scope, greeter) {
  // ...
}
MyController.$inject = ['$scope', 'greeter'];
someModule.controller('MyController', MyController);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

和內聯數組註解一樣,函數中的依賴參數順序要和$inject 屬性數組裏的順序一致。

隱式註解

注意:如果你想最小化你的JavaScript代碼時,如果使用了這種註解方式,service將會被重命名,程序就會報錯。

獲得依賴最簡單的方法就是假定函數參數的名字就是所需依賴的名字。

someModule.controller('MyController', function($scope, greeter) {
  // ...
});
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

給出一個函數,injector可以通過檢查函數聲明和提取參數名稱來確定需要注入的service的名字。 
這種註解方法的一個優點是,你不需要去關心數組中的元素和函數的參數是否一致,你可以自由排列參數的順序。 
但是你一旦對javascript做了最小化或者混淆操作,那麼函數將無法運行,因爲參數會被重命名。基於這些問題,我們建議避免這種風格的註解。

有一個好工具ng-annotate可以幫你在最小化前自動把你程序裏的所有的隱私註解更改爲內聯數組註解。 
還有如果你決定採取這種方法,您可能希望使用ng-strict-di。

使用ng-strict-di指令

你可以在和ng-app同一個元素中添加一個ng-strict-di指令。

<!doctype html>
<html ng-app="myApp" ng-strict-di>
<body>
  I can add: {{ 1 + 2 }}.
  <script src="angular.js"></script>
</body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在strict模式下,當一個服務試圖使用隱式註解時就會拋出一個錯誤。 
如下,包含了隱式註解的willBreak service。

angular.module('myApp', [])
.factory('willBreak', function($rootScope) {
  // $rootScope is implicitly injected
})
.run(['willBreak', function(willBreak) {
  // Angular will throw when this runs
}]);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

當willBreak初始化時就會報錯,因爲已經指定了strict模式。這可以用來保證ng-annotate工具已經把你程序的所有隱式註解修改爲了內聯數組註解。

如果你手動修改bootstrapping,你可以指定strictDi: true來達到同樣的效果。

angular.bootstrap(document, ['myApp'], {
  strictDi: true
});
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

爲什麼使用依賴注入

一個組件(對象或者函數)獲得它需要的依賴的方法只有三種

  1. 組件去創建這個依賴,通常使用new操作符
  2. 組件去查找一個依賴,引用一個全局變量
  3. 組件可以得到一個依賴傳遞在它需要使用的地方

前兩個方法都不是最佳的,因爲需要將依賴硬編碼到組件中。需要依賴需要修改的時候這就非常的困難。特別是在測試階段,爲了測試的隔離性,常常需要提供一個模擬的依賴。 
第三種方式是可行的,因爲它把依賴的定位查找的責任從組件中剝離出來,依賴僅僅是簡單地傳遞給組件。

function SomeClass(greeter) {
  this.greeter = greeter;
}

SomeClass.prototype.doSomething = function(name) {
  this.greeter.greet(name);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在上面的例子中SomeClass不需要關心greeter依賴的創建和查找,僅僅需要在實例化時獲得greeter依賴即可。它把獲得依賴的責任丟給了代碼結構設計。

爲了管理依賴創建的責任,每個angular應用都有一個injector,injector是一個負責創建和查找依賴的服務定位器。

下面是一個使用injector服務的例子:

/**Provide the wiring information in a module*/
var myModule = angular.module('myModule', []);
  • 1
  • 2
  • 1
  • 2

我們來看看injector如何創建greeter服務。注意greeter還依賴於$window服務,greeter服務是一個包含了greet函數的對象。

myModule.factory('greeter', function($window) {
  return {
    greet: function(text) {
      $window.alert(text);
    }
  };
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在myModule中創建一個可以提供組件的injector,並且從injector中獲得greeter服務。(這通常由angular的bootstrap完成)

var injector = angular.injector(['ng', 'myModule']);
var greeter = injector.get('greeter');
  • 1
  • 2
  • 1
  • 2

這樣雖然也解決了依賴的硬編碼問題,但是意味着injector需要穿過整個應用,這樣就打破了迪米特法則。爲了彌補這一點,我們在HTML模板使用聲明性符號把創建依賴的責任移交給injector,如下:

<div ng-controller="MyController">
  <button ng-click="sayHello()">Hello</button>
</div>
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
function MyController($scope, greeter) {
  $scope.sayHello = function() {
    greeter.greet('Hello World');
  };
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

當angular編譯這個html時,它處理到ng-controller,它將要求injector去創建controller實例和它的依賴。

injector.instantiate(MyController);
  • 1
  • 1

這些操作都是在後臺做的。通過ng-controller要求injector去實例化的方式,可以讓controller不知道injector的情況下,創建了MyController所有的依賴。應用程序無需理會injector,僅僅是簡單的聲明它所需要的依賴,這樣就不會打破迪米特法則。

整個過程圖示如下: 
這裏寫圖片描述

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