依赖注入(DI)是软件设计模式,用来处理组件怎样掌控它们的依赖关系。Angular注入子系统负责创建组件,解决它们的依赖关系,并把它们提供给其它需要的组件。
1)、使用依赖注入
依赖注入贯穿于Angular,可以在定义组件或给模提供run
和config
代码块时候使用它。
-
比如 services, directives, filters, and animations组件是通过可注入的Factory方法或构造函数定义。这些组件可以依赖注入一些 "service" 和"value" 组件。
-
Controllers通过构造函数定义, 可以依赖注 "service" 和"value"组件,但它们也就可以作为特殊依赖关系提供方。参考 下面的一系列此类特殊依赖关系的 Controllers 。
-
run
方法接受一个可以依赖注入 "service", "value" 和"constant" 组件的函数。注意你不能在run
块中注入 "providers" 。 -
config
方法接受一个可以依赖注入 "provider" 和"constant" 组件的函数。注意你不能在configuration中注入"service" 或 "value" 组件。
参考Modules 获取详细的关于
run
和config
代码块的信息。
1.1)、Factory 方法
跟定义 directive, service, 或 filter的方式一样定义factory 函数。factory 方法也带module注册。申明factories推荐的方式是:
angular.module('myModule', []) .factory('serviceId', ['depService', function(depService) { // ... }]) .directive('directiveName', ['depService', function(depService) { // ... }]) .filter('filterName', ['depService', function(depService) { // ... }]);
1.2)、Module 方法
可以为module指定函数在configuration时或run time时通过调用
config
和run
方法运行。这些函数可以被带依赖注入就像上面的 factory 函数。
angular.module('myModule', []) .config(['depProvider', function(depProvider) { // ... }]) .run(['depService', function(depService) { // ... }]);
1.3)、Controllers
Controllers 是"classes" 或"constructor functions" 负责提供应用行为,可以支持在template中申明标记。申明 Controllers推荐的方式是使用数组表示:
someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) { ... $scope.aMethod = function() { ... } ... }]);
不像services,在应用中可以有同类型controller的很多对象。
还有,额外的依赖对 Controllers也可用:
$scope
: Controllers跟DOM的元素有关,因而可以访问 scope。 其他组件 (例如services) 只能访问$rootScope
service。- resolves: 若controller初始化为route的一部分,那任何被解析为route的一部分的值能注入到controller。
这里
$inject
数组中的值的顺序必须匹配MyController
参数的顺序。就像数组注解,你需要注意保持
$inject
跟函数申明的参数同步。
2)、依赖注解
Angular 通过注入器调用某些函数 (例如service factories 和 controllers)。你需要注解这些functions来让注入器知道该个妞function注入什么services。有三种方法来使用service名字信息来注解你的代码:
- 使用内联数组注解(优选)
- 使用
$inject
属性注解 - 从函数参数名隐形注解(有注意事项)
2.1)、内联数组注解
这个是优选方法来注解应用组件。这是文档中例子写法。
例如:
someModule.controller('MyController', ['$scope', 'greeter', function($scope, greeter) { // ... }]);
这里传递一个数组,它的元素有字符串列表组成 (依赖关系名字)跟在function 后面。
当使用这种类型注解,注意保持注解数组与函数声明的参数同步。
2.2)、$inject 属性注解
为了允许minifiers重命名function parameters并仍可以注入正确的services, function需要跟
$inject
property一同注解。$inject
property释一个要注入的 service 名字数组。
var MyController = function($scope, greeter) { // ... } MyController.$inject = ['$scope', 'greeter']; someModule.controller('MyController', MyController);
这种情况下,
$inject
数组中值得顺序必须匹配MyController
.中参数的顺序。就像数组注解,你需要保持
$inject
与函数声明的参数同步。
2.3)、隐式注解
最简单的掌控依赖关系的方法是设想函数参数即为所依赖的名字。
someModule.controller('MyController',function($scope, greeter){// ...});
给定一个函数,注入器可以通过测验函数的申明和检查参数名字来推断要注入的services名字。在上述例子,
$scope
和greeter
是两个需要被注入到函数的services 。这个方法的一个优点是这没有任何名字数组与函数参数保持同步。你也可以自由排序依赖关系。
然而这个方法不会与JavaScript minifiers/obfuscators一起工作,因为他们重命名参数的方式。
像 ng-annotate 工具让你在应用中使用隐形依赖注解并在minifying之前自动添加内联数组注解。若你决定利用这个方法,你可以使用
ng-strict-di
。因为这些注意事项。建议避免使用这个注解风格。
3)、使用严格的依赖注入
你可以在与
ng-app
同样的element添加ng-strict-di
directive , 来打开严格的 DI 模式:<!doctype html>
<html ng-app="myApp" ng-strict-di>
<body>
I can add: {{ 1 + 2 }}.
<script src="angular.js"></script>
</body>
</html>
严格模式跑出错误每当 service 尝试使用 隐形注解。
参考这个module,包含 使用隐形DI的
willBreak
service:angular.module('myApp', [])
.factory('willBreak', function($rootScope) {
// $rootScope is implicitly injected
})
.run(['willBreak', function(willBreak) {
// Angular will throw when this runs
}]);
当
willBreak
service初始化,Angular 会因为严格模式抛出错误。当使用类似 ng-annotate 工具来确保所有应用组件带有注解。若正在使用人工引导,你能够在选项配置参数中提供
strictDi: true
来使用隐形DI:angular.bootstrap(document, ['myApp'], {
strictDi: true
});
3)、为何依赖注入?
这段深入解释Angular的DI使用。关于怎么用请看上文。
为深入探讨 DI,参考维基百科Dependency Injection ,Martin Fowle写的 Inversion of Control ,或者在你的软件设计模式书中阅读 DI 。
只有三种方式一个 component (object or function) 能掌握它的依赖关系:
- component 能够创建依赖,通常使用
new
操作。 - component 能够查询依赖,通过引用一个全局变量。
- component 能够在它需要的地方有依赖传过来。
- component 能够创建依赖,通常使用
前两个方法创建或查找依赖不是最优选,因为它们在component中硬编码。为了改变依赖关系会带来困惑。若不是不可能, 要修改依赖关系。尤其测试中更有问题,在那它经常为了测试隔离需要提供模拟关系。
第三个方法最可行,因为它去掉了从component定位依赖关系的义务。依赖关系仅仅传递给component。
function SomeClass(greeter) {
this.greeter = greeter;
}
SomeClass.prototype.doSomething = function(name) {
this.greeter.greet(name);
}
上面例子
SomeClass
不要关心创建或定位greeter
依赖,仅仅greeter初始化的时候传递。
这个示意图, 但它把掌控依赖的责任推给构造
SomeClass
.的代码。
为了管理dependency 创建的责任,每个 Angular 应用都有一个 injector。 injector 是一个 service locator 负责构造并查询依赖关系。
这个例子使用 injector service:
// Provide the wiring information in a module
var myModule = angular.module('myModule', []);
教 injector 怎么构建
greeter
service。注意greeter
依赖$window
service。greeter
service 是一个包含greet
method的对象。myModule.factory('greeter', function($window) {
return {
greet: function(text) {
$window.alert(text);
}
};
});
创建一个新的injector 能提供定义在
myModule
module的components 并从injector申请greeter
service。(通常由 angular bootstrap自动完成)。var injector = angular.injector(['ng', 'myModule']);
var greeter = injector.get('greeter');
要求依赖关系,解决了硬编码问题,但它也意味着 injector 需要始终传到应用。传递 injector 破坏了 Law of Demeter。为了补救,在HTML templates中使用declarative 符号,来担当越过injector创建的责任,例如此例:
<div ng-controller="MyController">
<button ng-click="sayHello()">Hello</button>
</div>
function MyController($scope, greeter) {
$scope.sayHello = function() {
greeter.greet('Hello World');
};
}
当Angular编译 HTML,它处理
ng-controller
directive,反过来要求 injector创建 controller 和它的依赖关系的对象。injector.instantiate(MyController);
所有都在幕后完成,注意拥有
ng-controller
要求njector初始化 class,它可以满足MyController
所有的i依赖关系,而不需要controller熟悉injector。最佳结果,应用代码code 仅仅申明它的依赖关系,而不用跟 injector交涉。这种设置不会破坏 Law of Demeter。
Note: Angular 使用 constructor injection.