依賴注入
依賴注入(DI)是一種軟件設計模式,處理組件如何獲取其依賴關係。
AngularJS注入器子系統負責建立組件,解析它們的依賴關係,並根據請求將它們提供給其餘組件。
使用依賴注入
DI遍及AngularJS。 能夠在定義組件或爲模塊提供運行和配置塊時使用它。html
諸如服務,指令,過濾器和動畫之類的組件由注入工廠方法或構造函數定義。 這些組件能夠注入「服務」和「值」組件做爲依賴關係。bootstrap
控制器由構造函數定義,可使用任何「服務」和「值」組件做爲依賴關係注入,可是它們也能夠提供特殊的依賴關係。設計模式
run方法接受一個函數,它能夠注入"service","value"和"constant"組件做爲依賴。 請注意,不能將"providers"注入運行塊。數組
config方法接受一個函數,它能夠注入"provider"和"constant"組件做爲依賴。 請注意,沒法將「服務」或「值」組件注入配置。app
工廠方法
定義指令,服務或過濾器的方式是使用工廠函數。 工廠方法使用模塊註冊。 推薦的工廠聲明方式是:ide
angular.module('myModule', []) .factory('serviceId', ['depService', function(depService) { // ... }]) .directive('directiveName', ['depService', function(depService) { // ... }]) .filter('filterName', ['depService', function(depService) { // ... }]);
模塊方法
咱們能夠經過調用config和run方法指定要在模塊的配置和運行時運行的函數。 這些函數是可注入的依賴關係,就像上面的工廠函數。函數
angular.module('myModule', []) .config(['depProvider', function(depProvider) { // ... }]) .run(['depService', function(depService) { // ... }]);
控制器
控制器是「類」或「構造函數」,它們負責提供支持模板中的聲明性標記的應用程序行爲。 推薦的聲明控制器的方法是使用數組符號:工具
someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) { ... $scope.aMethod = function() { ... } ... }]);
與服務不一樣,應用程序中可能有許多相同類型的控制器實例。測試
此外,對控制器提供附加的依賴性:動畫
$scope:控制器與DOM中的元素相關聯,所以提供對該做用域的訪問。 其餘組件(如服務)只能訪問$rootScope服務。
resolves:若是將控制器實例化爲路由的一部分,則做爲路由的一部分解析的任何值均可用於注入到控制器中。
依賴註解
AngularJS經過注入器調用某些功能(如服務工廠和控制器)。 須要註解這些函數,以便注入器知道要注入到函數中的服務。 有三種方法用服務名稱信息註解代碼:
使用內聯數組註解(首選)
使用$inject屬性註解
隱含地從函數參數名稱(有警告)
內聯數組註解
這是註解應用程序組件的首選方式。 這是文檔中的示例如何編寫。
例如:
someModule.controller('MyController', ['$scope', 'greeter', function($scope, greeter) { // ... }]);
這裏咱們傳遞一個數組,其元素由一個字符串列表(依賴項的名稱)和函數自己組成。
當使用這種類型的註解時,注意保持註解數組與函數聲明中的參數同步。
$inject屬性註解
爲了容許簡寫重命名函數參數並仍然可以注入正確的服務,該函數須要使用$inject屬性註解。 $inject屬性是要注入的服務名稱的數組。
var MyController = function($scope, greeter) { // ... } MyController.$inject = ['$scope', 'greeter']; someModule.controller('MyController', MyController);
在這種狀況下,$inject數組中的值的順序必須與MyController中的參數的順序匹配。
就像數組註解同樣,你須要注意保持$inject與函數聲明中的參數同步。
隱式註解
注意:若是計劃縮小代碼,你的服務名稱將被重命名並破壞你的應用程序。
someModule.controller('MyController', function($scope, greeter) { // ... });
給定一個函數,注入器能夠經過檢查函數聲明並提取參數名稱來推斷要注入的服務的名稱。 在上面的例子中,$scope和greeter是須要注入到函數中的兩個服務。
這種方法的一個優勢是沒有與函數參數保持同步的名稱數組。 能夠自由地從新排序依賴項。
可是這個方法不能使用JavaScript minifiers / obfuscators,由於他們會重命名參數。
像ng-annotate這樣的工具能夠在應用程序中使用隱式依賴關係註解,並在縮小以前自動添加內聯數組註解。 若是你決定採起這種方法,你可能想使用ng-strict-di。
因爲這些注意事項,咱們建議避免使用此樣式的註解。
使用嚴格依賴注入
能夠在與ng-app相同的元素上添加ng-strict-di指令以選擇進入嚴格DI模式:
<!doctype html> <html ng-app="myApp" ng-strict-di> <body> I can add: {{ 1 + 2 }}. <script src="angular.js"></script> </body> </html>
只要服務嘗試使用隱式註解,嚴格模式就會拋出錯誤。
考慮這個模塊,其中包括一個使用隱式DI的willBreak服務:
angular.module('myApp', []) .factory('willBreak', function($rootScope) { // $rootScope是被隱式註解 }) .run(['willBreak', function(willBreak) { //當它運行時AngularJS拋出這個異常 }]);
當willBreak服務被實例化時,AngularJS將拋出一個錯誤,由於嚴格的模式。 當使用ng-annotate等工具確保全部應用程序組件都有註解時,這是很是有用的。
若是使用手動引導,還能夠經過在可選配置參數中提供strictDi:true來使用嚴格的DI:
angular.bootstrap(document, ['myApp'], { strictDi: true });
爲何要依賴注入?
這部分激勵和解釋AngularJS使用DI。有關如何使用DI,請參見上文。
組件(對象或函數)只有三種方式能夠得到它的依賴:
組件能夠建立依賴關係,一般使用new運算符。
組件能夠經過引用全局變量來查找依賴關係。
組件能夠在須要的地方傳遞依賴。
建立或查找依賴關係的前兩個選項不是最優的,由於它們會對組件的依賴性進行硬編碼。這使得很難,即便不是不可能,修改依賴性。這在測試中尤爲成問題,其中一般指望提供用於測試隔離的模擬依賴性。
第三個選項是最可行的,由於它消除了從組件定位依賴性的責任。依賴項簡單地交給組件。
function SomeClass(greeter) { this.greeter = greeter; } SomeClass.prototype.doSomething = function(name) { this.greeter.greet(name); }
在上面的例子中,SomeClass不關心建立或定位greeter依賴,它只是在實例化時傳遞給greeter。
這是可取的,但它負責獲取構造SomeClass的代碼的依賴。
爲了管理依賴項建立的責任,每一個AngularJS應用程序都有一個注入器。 注入器是負責構建和查找依賴性的服務定位器。
如下是使用注入器服務的示例:
// 在模塊中提供鏈接信息 var myModule = angular.module('myModule', []);
指示注入器如何創建一個greeter服務。 注意greeter依賴於$window服務。 greeter服務是一個包含greet方法的對象。
myModule.factory('greeter', function($window) { return { greet: function(text) { $window.alert(text); } }; });
建立一個新的注入器,它能夠提供myModule模塊中定義的組件,並從注入器請求咱們的greeter服務。 (這一般由AngularJS引導自動完成)。
var injector = angular.injector(['ng', 'myModule']); var greeter = injector.get('greeter');
請求依賴性解決了硬編碼的問題,但它也意味着注入器須要在整個應用程序中傳遞。 經過注入器打破了Demeter的定律。 爲了彌補這一點,咱們在HTML模板中使用聲明符號,將建立組件的責任交給注入器,以下例所示:
<div ng-controller="MyController"> <button ng-click="sayHello()">Hello</button> </div>
function MyController($scope, greeter) { $scope.sayHello = function() { greeter.greet('Hello World'); }; }
當AngularJS編譯HTML時,它會處理ng-controller指令,而後請求注入器建立控制器及其依賴關係的實例。
injector.instantiate(MyController);
這一切都在幕後完成。 注意,經過讓ng-controller要求注入器實例化類,它能夠知足MyController的全部依賴性,而控制器不知道注入器。這是最好的結果。 應用程序代碼簡單地聲明它須要的依賴性,而沒必要處理注入器。 這個設置不打破德米特法。