NO END FOR LEARNING

Writing blog if you feel tired | 学海无涯 苦写博客

开始!AngularJS!(六)- 依赖注入

| Comments

不说废话,开始学习AngularJS的依赖注入,如果你对什么是依赖注入还不明白的话,可以看看Martin Fowler的一篇关于依赖注入的文章 Inversion of Control Containers and the Dependency Injection pattern,这里也有译文

依赖注入就是,在需要此依赖的地方等待被依赖对象注入(传入)进来,而不是通过new关键字去构造,或者去查找某个依赖。

看一个AngularJS依赖注入的例子:

1
2
3
4
5
6
<body ng-app="diApp">
    <div ng-controller="diController">
        <input type="text" ng-model="alertValue" />
        <input type="button" ng-click="alertMe()" value="clickMe" />
    </div>
</body>
1
2
3
4
5
6
7
angular.module('diApp', [])

.controller('diController', function ($scope, $window) {
    $scope.alertMe = function () {
        $window.alert($scope.alertValue);
    };
});

在jsfiddle中查看,http://jsfiddle.net/n5sknpe9/

在定义控制器diController时,在构造函数中传入一个对象$scope和一个服务$window。$scope将控制器作用域中的模型alertValue传递进来,而$window则提供alert()方法。

$inject

用起来看着确实很简单,那么AngularJS是怎么做到的呢?看下面一个例子。

1
2
3
4
5
<body ng-app="diApp">
    <div ng-controller="diController">
        { {value} }
    </div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
angular.module('diApp', [])

.factory('diService', function () {
    return {
        getValue: function(){
            return 1 + 1;
        }
    };
})

.controller('diController', function($scope, diService){
    $scope.value = diService.getValue();
});

每个Angular应用都有一个injector对象。这个injector是一个服务定位器,负责创建和查找依赖,当你的app的某处声明需要用到某个依赖时,Angular 会调用这个依赖注入器去查找或是创建你所需要的依赖,然后返回来给你用。

为了看得更清楚,手动的去调injector来获取该依赖,就下面这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
angular.module('diApp', [])

.factory('diService', function () {
    return {
        getValue: function(){
            return 1 + 1;
        }
    };
});

var injector = angular.injector(['diApp', 'ng']);

var diService = injector.get('diService');

console.log(diService.getValue());

而当你在声明说需要的依赖时,AngularJS帮你做了上面这件事情。

依赖注解(Annotation)的方式

那么$inject服务,怎么知道应该将什么依赖注入给你呢?

如果从$inject服务的内部来看,有下面三种方式:

1
2
3
4
5
6
7
8
9
10
// inferred (only works if code not minified/obfuscated)
$injector.invoke(function(serviceA){});

// annotated
function explicit(serviceA) {};
explicit.$inject = ['serviceA'];
$injector.invoke(explicit);

// inline
$injector.invoke(['serviceA', function(serviceA){}]);

那么,换成在注册控制器或者服务时,对应也是三种方式:

直接指定,最简单的获取依赖的方法是让你的函数的参数名直接使用依赖名,也就是之前的那些例子一样。

1
2
3
function myController($scope, myService) {
    ...
}

$inject服务的官方例子也说了,这种用法只适合于js不需要压缩和混乱的情况下。

而为了让在压缩版的js代码能中重命名过的参数名能够正确地注入相关的依赖服务。函数需要通过$inject属性进行标注,这个属性是一个存放需要注入的服务的数组。

1
2
3
4
var myController = function(renamed$scope, renamedMyService) {
    ...
}
myController['$inject'] = ['$scope', 'myService'];

但是,这种方式是不是看的很累赘。

还有最后一种方法,内联(inline)

1
2
3
angular.module('myApp',[]).controller('myController',['$scope','myService',function($scope, myService) {
    ...
}]);

这样是不是好多了,这是AngularJS官方推荐的方法,我之前写的例子,都不算是最佳实践,我们应该参考这种方式去实现自己控制器和服务。

总算,将依赖注入的部分介绍完了,下一节,一起来了解AngularJS为View Model(视图模型)提供的强大的过滤器功能。

参考资料:
1.http://www.ngnice.com/docs/guide/di
2.http://www.ngnice.com/docs/api/auto/service/$injector
3.http://www.ngnice.com/docs/tutorial/step_05

开始!AngularJS!(五)- 模块化

| Comments

我们都知道JavaScript很容易就写出全局函数,所以无论是用jQuery还是纯JavaScript,我们都会使用模块化的策略避免写出来的函数污染全局。

而模块化的办法一般都是利用函数对象立即执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var feature = (function() {

    // Private variables and functions
    var privateThing = "secret";
    var publicThing = "not secret";

    var changePrivateThing = function() {
        privateThing = "super secret";
    };

    var sayPrivateThing = function() {
        console.log( privateThing );
        changePrivateThing();
    };

    // Public API
    return {
        publicThing: publicThing,
        sayPrivateThing: sayPrivateThing
    };
})();

feature.publicThing; // "not secret"

// Logs "secret" and changes the value of privateThing
feature.sayPrivateThing();

模块与ng-app指令

Angular也有模块的概念,但是它不只是为了解决全局函数的问题。

看一个最简单的例子:

1
2
3
4
5
<body ng-app="textApp">
    <div ng-controller="textController">
        <input type="text" ng-model="name" />
    </div>
</body>
1
2
3
4
5
angular.module('textApp', [])

    .controller('textController', function ($scope) {
        $scope.name = 'Benwei';
    });

在jsfiddle中查看,http://jsfiddle.net/benweizhu/73kn66kd/

在前面我们已经无数次的看到ng-app指令。它的作用是自动启动一个AngularJS应用,ng-app指令一般指派在应用的根元素上,比如,body或者html标签。在每一个HTML文档中,只能有一个AngularJS应用可以被自动启动,在HTML文档中第一个被找到定义在根元素上的ng-app指令将会作为自动启动的应用。

可以理解它为AngularJS应用启动的触发点。

那我们在js代码中定义的模块和ng-app有什么关系呢?很明显,它是告诉AngularJS应用在启动时加载指定的模块,假设这里ng-app只是放一个纯标签,而不给它赋值。那么它就不知道这里该加载什么模块,于是,它也不认识在模块中定义的textController控制器。

但是,赋值与否和启动一个AngularJS的应用无关:

1
2
3
<body ng-app>
     <input type="text" ng-model="name" />
</body>

这样也是可以启动AngularJS应用,并实现name模型的绑定。

模块化的目的

模块化除了像Java中分包,分类那样来划分职责,还带来什么好处呢?(以下摘自官方文档)

整个过程是声明式的,更容易理解
在单元测试中,没有必要加载所有模块,这样有利于单元测试的代码书写
在场景测试中,额外的模块可以被加载进来,进而重写一些配置,这样有助于实现应用的端到端的测试
第三方代码可以很容易被打包成可重用的模块
模块可以用任意顺序或并行顺序加载(得益于模块执行的延迟性)

子模块

对于大型应用,根据不同职责,建议把它像这样分成多个模块(参考官方文档):

服务模块
指令模块
过滤器模块
一个应用的模块,依赖于上述的三个模块,而且包含应用的初始化及启动代码

模块依赖

模块声明时可以列出它所需要依赖的其它模块。一个模块依赖某模块,意味着这个被依赖的模块需要在模块被加载之前加载完毕。更具体些,假设模块A依赖于模块B,那么模块A的配置代码块的执行,必须发生在模块B的配置代码块之后;模块A的执行代码块亦同理,也在模块B的执行代码块之后被执行。每个模块只能被加载一次,即使有多个别的模块依赖它。

创建模块,获取模块,如何实现模块的依赖

使用 angular.module(‘myModule’, []) 将创建名为 myModule 的模块并重写已有的同名模块。而使用 angular.module(‘myModule’) 则只会获取已有的模块实例。

你会注意到,创建和获取的区别,在于module函数的第二参数,该参数的作用是定义创建模块时的所依赖的模块(子模块)。

这样做的好处是,不同的服务或者一组服务被放置在不同的可重用模块,那么应用模块只需要声明应用所需要的全部依赖模块即可。

来看一个模块依赖的例子:

1
2
3
4
5
6
7
8
9
10
11
angular.module('restApp', ['ngResource'])
    .factory('personRest', function ($resource) {
        return $resource('/app/person');
    });

angular.module('myApp', ['restApp'])
    .controller('parentsController', function ($scope, personRest) {
        personRest.get({}, function (data) {
            $scope.person = data.person;
        });
    });

我需要写一个Rest的客户端模块restApp,需要使用到AngularJS提供的$resource服务,那么首先我需要引入AngularJS的ngResource服务模块,最后在我的应用模块,引入我自己定义的restApp模块然后,我就可以注入我自己定义personRest服务,继而使用它,这是一个典型的模块依赖例子。

本来上一节,说好了要讲解依赖注入,但是我发现先介绍模块和模块化更好,一个是尽快帮助学习写更规范的AngularJS应用,二个也会有助于之后对理解依赖注入。下一节,我们来讨论AngularJS中的依赖注入。