AngularJS 依赖注入

什么是依赖注入?

是一种软件设计模式,在这种模式下,一个或更多的依赖(或服务)被注入(或者通过引用传递)到一个独立的对象(或客户端)中,然后成为了该客户端状态的一部分。

一个对象通常有三种方式可以获得对其依赖的控制权:
(1) 在内部创建依赖;
(2) 通过全局变量进行引用;
(3) 在需要的地方通过参数进行传递
依赖注入是通过第三种方法实现的。其余两种都会带来各种问题,例如污染全局作用域,使隔离变得异常困难等。依赖注入是一种设计模式,它可以去除对依赖关系的硬编码,从而可以在运行时改变甚至移除依赖关系。

AngularJS使用 injector (注入器服务)来管理依赖关系的查询和实例化。事实上, injector 负责实例化AngularJS中所有的组件,包括应用的模块、指令和控制器等。
在运行时,任何模块启动时 $injector 都会负责实例化,并将其需要的所有依赖传递进去。

下面这段代码声明了一个模块和控制器:

// 这是一个简单的应用,声明了一个模块和一个控制器:
    angular.module('myApp', [])
        .factory('greeter', function() {
            return {
                greet: function(msg) {alert(msg);}
            }
        })
        .controller('MyController',
            function($scope, greeter) {
                $scope.sayHello = function() {
                    greeter.greet("Hello!");
                };
            });

当AngularJS实例化这个模块时,会查找 greeter 并自然而然地把对它的引用传递进去:

<div ng-app="myApp">
    <div ng-controller="MyController">
        <button ng-click="sayHello()">Click me quickly!</button>
    </div>
</div>

而在内部,AngularJS的处理过程是下面这样的:

// 使用注入器加载应用
var injector = angular.injector(['ng', 'myApp']);
// 通过注入器加载$controller服务:var $controller = injector.get('$controller');
var scope = injector.get('$rootScope').$new();
// 加载控制器并传入一个作用域,同AngularJS在运行时做的一样
var MyController = $controller('MyController', {$scope: scope})

上面的代码中并没有说明是如何找到 greeter 的,但是它的确能正常工作,因为 $injector会负责为我们查找并加载它。

行内注入声明

AngularJS提供的注入声明的最常用的一种方式,是可以随时使用的行内注入声明。这种方式其实是一个语法糖,它同前面提到的通过 $inject 属性进行注入声明的原理是完全一样的,但允许我们在函数定义时从行内将参数传入。此外,它可以避免在定义过程中使用临时变量。

在定义一个AngularJS的对象时,行内声明的方式允许我们直接传入一个参数数组而不是一个函数。数组的元素是字符串,它们代表的是可以被注入到对象中的依赖的名字,最后一个参数就是依赖注入的目标函数对象本身。

示例:

angular.module('myApp')
    .controller('MyController', ['$scope', 'greeter', function($scope, greeter) {
    }]);

由于需要处理的是一个字符串组成的列表,行内注入声明可以在压缩后的代码中正常运行。通常通过括号和声明数组的 [ ] 符号来使用它。

ngMin

在实际生产过程中,当代码体积变得非常庞大时,写代码还要关心参数顺序将是一个耗费心力的工作。

通过使用 ngMin 这个工具,能够减少我们定义依赖关系需的工作量。 ngMin 是一个为AngularJS应用设计的预压缩工具,它会遍历整个AngularJS应用并帮助我们设置好依赖注入。

例如,它会将如下代码:

angular.module('myApp', [])
    .directive('myDirective', function($http) { })
    .controller('IndexController', function($scope, $q) {
    });

转换成下面的形式:

angular.module('myApp', [])
.directive('myDirective', ['$http', function ($http) { }])
.controller('IndexController', [ '$scope', '$q',function ($scope, $q) {} ]);

ngMin 可以显著减少代码输入的工作量,并保持源文件的整洁。

1 安装

可以通过npm包管理工具来安装ngMin:

$ npm install -g ngmin

如果正在使用Grunt,我们可以安装 grunt-ngmin 插件。如果正在使用Rails,也可以通过Ruby的包管理工具 gem来安装 ngmin-rails 。

2 使用 ngMin

我们可以在命令行界面单独使用 ngMin ,可以通过标准输入输出设备或标准输出流传入
input.js和output.js两个参数,例如:

$ ngmin input.js output.js
#或者
$ ngmin < input.js > output.js

input.js是源文件,而 output.js则是转换过注入声明后的输出文件。

3 工作原理

在其内部, ngMin 使用抽象语法树(Abstract Syntax Tree,AST)来遍历JavaScript源代码。借助名为 astral 的AST工具框架的帮助,它可以将必要的声明代码添加进源文件,并用 escodegen将转换后的源文件输出。