本文翻译自angular官网的依赖注入讲解。
依赖注入(DI)是一种用来解决组件如何获取其依赖的软件设计模式。
angularJS注入子系统负责创建组件,解析组件的依赖,并且把他们组作为依赖提供给其他需要的组件。
DI贯穿整个angularJS。在定义组件或者给module提供run和config模块的时候你可以使用依赖注入。
@1:像services,directives,filters和animations这些组件都是通过注入工厂函数或者构造函数来定义的。这些组件可以注入“service”和“value”作为依 赖。
@2:controllers通过构造函数定义,它可以被注入任何“service”和“value”作为依赖,并且它还可以作为特殊的依赖被注入。
@3:run方法接收一个函数,这个函数可以被注入“service”,“value”和“constant”。注意:你不能注入“providers”到一个run模块中。
@4:config方法接收一个函数,这个函数可以被注入“providers”和“constant”。注意:你不能注入“service”和“value”到一个config模块中。
使用factory方法可以定义一个directive,service,或者filter。factory方法要通过module注册。推荐的factory声明方式如下:
angular.module('myModule', []) .factory('serviceId', ['depService', function(depService) { // ... }]) .directive('directiveName', ['depService', function(depService) { // ... }]) .filter('filterName', ['depService', function(depService) { // ... }]);
我们可以通过调用config和run方法来指定一个函数在一个module配置和运行的时候来执行。这些方法依赖的注入方法和上面的factory方法一样:
angular.module('myModule', []) .config(['depProvider', function(depProvider) { // ... }]) .run(['depService', function(depService) { // ... }]);
controllers是用来提供application应用的行为逻辑来支持模板中定义的标签,controllers是一些类或者构造函数。声明controller的推荐方式是使用数组注解:
someModule.controller('MyCtrl', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) { ... $scope.aMethod = function() { ... } ... }]);
和services不同,在应用中可以有很多相同类型的controller。
另外,controller中还可以注意一些额外的对象:
@1:$scope ,Controller和DOM中的元素相关联,所以被设计成可以接收scope对象,其他的组件(像services)只接收$rootScope服务。
@2:resolves,如果Controller实例是route路由的一部分,那么作为路由一部分支持的任何变量都是可以注入到Controller的。
AngularJS 通过注入器调用指定的函数,诸如service函数和控制器函数。你应该通过注解指明这些函数的名称,以便注入器知道那些服务需要注入到函数中。这里有三种注解来指明服务的名称:
@1:使用数组注解(推荐)
@2:使用$inject属性注解
@3:通过函数的参数名称隐式注解(有缺陷)
这是应用组件的最优注解方式。例子如下:
someModule.controller('MyCtrl', ['$scope', 'greeter', function($scope, greeter) { // ... }]);
这里我们传入了一个数组,数组是有字符串列表和一个函数组成的,字符串是依赖的名称。
当你使用这种注解的时候,注意一定要保证注解数组中的字符串和函数的参数对应一致。
为了能在压缩文件的时候保证注入正确的服务,方法需要通过$inject的属性来进行注解,$inject的属性是一个用来注入的服务名称的数组。
var MyController = function($scope, greeter) { // ... } MyController.$inject = ['$scope', 'greeter']; someModule.controller('MyController', MyController);
这种注入方案$inject的数组元素的顺序要和myController方法参数的顺序保持一致。
和数组注入一样,也要保证$inject和方法声明中的参数一致。
注意:如果你打算压缩你的代码,你的服务将会被重命名导致应用中断出错。
最简单获取依赖的方式就是假定方法的参数名就是依赖的服务的名称。
someModule.controller('MyCtrl', function($scope, greeter) { // ... });
注入器可以通过检测一个方法的声明提取参数名称来推断一个方法需要注入的服务。在上面的例子中,$scope和greeter就是两个需要被注入到方法中的服务。
这种方法的一个好处就是不用考虑数组元素名称和方法参数保持一致,你可以随意注入依赖。
然而,这种方法中javaScript压缩或者混淆时改变了参数名称后就不能正常运行了。
像ng-annotate这种工具可以让你放心使用隐式注解,它会在压缩之前自动添加数组注解。如果你决定使用这种方式,你应该使用ng-strict-di.
因为有这些缺点,我们不建议使用这种注解形式。
你可以通过添加“ng-strict-di”指令到 "ng-app" 所在的元素上来启用严格依赖注入模式:
<!doctype html> <html ng-app="myApp" ng-strict-di> <body> I can add: {{ 1 + 2 }}. <script src="angular.js"></script> </body> </html>
如果一个服务想要使用隐式注解,那么严格模式会抛出一个异常。
考虑这种module,包含一个使用严格模式的 willBreak服务。
angular.module('myApp', []) .factory('willBreak', function($rootScope) { // $rootScope is implicitly injected }) .run(['willBreak', function(willBreak) { // AngularJS will throw when this runs }]);
当willBreak服务实例化的时候,angularJS会跑出一个严格模式的错误。这对于像ng-annotate工具去确定你应用中的所有组件是否有注解时是非常有用的。
如果你通过手动启动angular,你可以通过“strictDi:true”作为配置参数来使用严格模式的依赖注入:
angular.bootstrap(document, ['myApp'], { strictDi: true });
这部分讲解angularJS使用DI的动机和原因。
一个组件获取依赖的方法只有三种:
@1:组件创建依赖,通常使用new操作符
@2:组件查找依赖,通常通过一个全局变量引用
@3:作为参数传递组件需要的依赖
前两种方式不是最佳途径,因为他们把依赖和组件绑死了,属于硬编码。它使得修改依赖变得很困难,在依赖隔离的测试中,它的问题尤为明显。
第三种方式最可行,因为它不是在组件中定义的依赖。通过参数把依赖传递给组件很简便。
function SomeClass(greeter) { this.greeter = greeter; } SomeClass.prototype.doSomething = function(name) { this.greeter.greet(name); }
在上面的例子中,SomeClass不用考虑创建或者查找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服务。
var injector = angular.injector(['ng', 'myModule']); var greeter = injector.get('greeter');
我们的目标是摆脱硬编码,但它还是需要把注入器传递到应用中。为了纠正这个问题,我们在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指令,这个controller反过来请求注入器创建一个controller的实例并且注入依赖。
injector.instantiate(MyController);
这些都在幕后执行。注意通过ng-controller请求注入器来实例化控制器,它可以满足myController的所有依赖。
这是最好的实现方式,应用代码可以简单的声明他们所需要的依赖,不用和注入器协商。
note:angularJS使用构造函数注入器