0%

依赖注入

依赖注入是一种软件设计模式,他允许你移除软件组件的硬编码方式,替代的是通过依赖注入制造低耦合的组件不论在编译阶段还是在运行阶段。硬编码就是在程序中将代码写死,低耦合就是尽量让每个模块独立,相关的处理尽量在单个模块中完成。


AngularJs有一个内在的注入机制,他可以把你的App分成许多个可重复使用的组件,当需要的时候通过依赖注入把这些自减注入进你的App中去。在需要的地方进行参数传递,这种方法不仅对测试很有用,而且还不会污染全局变量,是很好的设计模式。


AngularJS依赖注入的方法

  1. 通过函数的参数进行推断式注入声明

    如果没有明确的声明,AngularJS会假定名称就是依赖的名称。因此,它会在内部调用函数对象的toString()方法,分析并提取函数的参数列表,然后通过$injector将这些参数再注入进对象实例。下面是代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function myController($scope,$timeout){
    var updateTime = function(){
    $scope.clock = {
    time: new Date()
    };
    $timeout(function(){
    $scope.clock.time = new Date();
    updateTime();
    },1000)
    }
    }
    1
    2
    3
    <div ng-controller='myController'>
    <span>{{clock.time | data:'yyyy-MM-dd hh:mm:ss'}}</span>
    </div>

    创建了一个可以自动更新时间的应用,看看是如何进行依赖注入的。通过设置参数$scope和$timeout,angular会在内部调用函数的toString()方法,分析并提取函数的参数列表,然后通过$injector将这些参数注入到对象的实例。



    注意:

  • 此方法只适合未经压缩混淆的代码,因为angular需要解析未经压缩混淆的参数列表。

  1. 显式的注入声明

    显式的明确定义一个函数在被调用时需要用到的依赖关系,通过这种方法声明依赖,即使在源代码被压缩,参数名称发生改变的情况下依然可以工作。代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var mycontrollerFactory = function mycontroller($scope,$timeout){
    var updateTime = function () {
    $scope.clock = {
    time: new Date()
    };
    $timeout(function () {
    $scope.clock.time = new Date();
    updateTime();
    }, 1000);
    }
    updateTime();
    }
    1
    mycontrollerFactory.$inject = ['$scope','$timeout'];

    显式的将我们需要的依赖注入到函数中,所以在函数中参数也可以分别换成其他字段。

    1
    var mycontrollerFactory = function mycontroller(s,t){.....}
    注意:

  • 对于这种声明方式,参数的顺序是十分重要的,因为$inject数组元素的顺序必须和注入的参数顺序一一对应。

  1. 行内注入声明

    angular提供的行内注入方法实际上是一种语法糖,它与前面的提到的通过$inject属性进行声明的原理是一样的,但是允许我们在函数定义的时候从行内将参数传入,这种方法方便简洁,而且避免了在定义的过程中使用临时变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    angular.module('app',[])
    .controller('mycontroller',['$scope','$timeout',function($scope,$timeout){
    var updateTime = function () {
    $scope.clock = {
    time: new Date()
    };
    $timeout(function () {
    $scope.clock.time = new Date();
    updateTime();
    }, 1000);
    }
    updateTime();
    }])
    注意:

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

下面来对比一下ng1与ng2的依赖注入的区别,以login组件为例

先来看一下angular2的架构图

providerToservice


Angular1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Login{
formValue : {login:string,password:string} = {login:'',password:''};
onSubmit(){
const service = new LoginService();
service.login(this.formValue);
}
}

module.component('login',{
controller: Login,
controllerAs ; 'ctrl',
template:`
<form ng-submit = "ctrl.onSubmit()">
Text <input type="text" ng-model="ctrl.formValue.login>
password <input type="password" ng-model="ctrl.formValue.password">
<button>submit</button>
</form>
`
})

Angular2
1
2
3
4
5
6
7
8
9
10
11
12
@Component({
selector:'login',
template:`
.....
`
})
class Login{
onSubmit(formValue:{login:string,password:string}){
const service = new LoginService();
service.login(formValue);
}
}

在这里直接把login组件绑定在login service上的,很难进行独立测试,也降低了它复用的可能性


with DI

在构造函数里面注射一个LoginService的实例,而不是直接创建

Angular1

1
2
3
4
5
6
7
class Login{
formValue : {login:string,password:string} = {login:'',password:''};
constructor(public service:LoginService){}
onSubmit(){
this.service.login(this.formValue);
}
}

我们还需要告诉框架应该创建这个service的实例

1
module.service('login',LoginService);



Angular2

同样,在构造函数里面注射一个LoginSerivce的实例

1
2
3
4
5
6
class Login{
constructor(public service: LoginService){}
onSubmit(formValue:{login:string,password:string}){
this.service.login(formValue);
}
}
  • 不同于ng1,我们需要将这个service添加到providers列表里面来实现
    1
    2
    3
    4
    5
    6
    @NgModule({
    bootstrap:[Login],
    providers:[LoginService],
    declarations:[Login]
    })
    class AppModule{}

    如果注入到它的根模块,则整个应用都可以调用,也可以注入到使用它的组件元数据里面,只需在装饰器加上一个providers配置项,这样注入的服务只对自己和后代可用

    1
    2
    3
    4
    5
    @Component({
    selector:'lr',
    providers:[LoginService]
    })
    class APPLr{}
    注意:
  • ng1依赖于字符串来配置DI,而ng2则默认使用注解的方式

    ng1里面有好几个Api可以用来给指令注入依赖,有些是根据名称注入的(LoginService),有些依赖会一直自动提供(link函数里面的),有些需要使用require进行配置


    ng2提供了统一的Api用来注入服务,指令等,所有这些内容都会被注入到组件的构造函数里面

    总结

  • DI是ng的核心机制之一
  • 他可以使你的代码更加松耦合
  • 提升了可测试性
  • ng2采用了统一的Api来给组件注入依赖