NO END FOR LEARNING

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

开始!AngularJS!(四)- 了解控制器

| Comments

只要你做过Web开发的,控制器的概念对你来说,实在是太熟悉了。在Spring MVC中,我认为控制器的职责应该只是告诉我,请求从哪里来,带来什么,要到哪里去,带走了什么。

至于要做什么事情,那是服务层的事情。

在AngularJS中,一般情况下,我们使用控制器做两件事:

1.初始化$scope对象
2.为$scope对象添加行为

在前面的章节中,我们已经知道控制器和作用域$scope的关系紧密。当某个控制器通过ng-controller指令被添加到DOM中时,AngularJS会调用该控制器的构造函数来生成一个控制器对象,这样,就创建了一个新的子级作用域。在这个构造函数中,作用域会作为$scope参数注入其中,并允许用户代码访问它。

初始化$scope对象

为Angular的$scope对象设置初始状态,通过在$scope对象上添加属性即实现。这些属性就是在视图中展示的视图模型(view model),与此控制器相关的模板中均可以访问到它们。

创建控制器

Angular允许我们在全局作用域下(window)定义控制器函数,就像在第二节中介绍的那样,定义一个JavaScript的全局函数函数。但不建议这种方式,推荐在Angular模块 下通过.controller为你的应用创建控制器,如第三节中那样。

1
2
3
4
5
6
7
<body ng-app="controllerSampleApp">
    <div ng-controller="sampleController">
        first name <input type="text" ng-model="firstName" /><br/>
        last name<input type="text" ng-model="lastName" /><br/>
        my name is .
    </div>
</body>
1
2
3
4
5
6
7
angular.module("controllerSampleApp", [])

.controller("sampleController", function ($scope) {
    $scope.fullName = function () {
        return $scope.firstName + " " + $scope.lastName;
    };
});

在jsfiddle中查看,http://jsfiddle.net/benweizhu/3whwfj3n/

明白控制器的作用,正确的使用控制器

就像上面所说的,通常情况下,控制器不应被赋予太多的责任和义务,它只需要负责一个单一视图所需的业务逻辑。最好的保持控制器干净的办法是将那些不属于控制器的逻辑都封装到服务(services)中,然后在控制器中通过依赖注入调用相关服务,这部分会在下一节介绍。

注意,下面的场合千万不要用控制器:(引用官方文档)

任何形式的DOM操作:控制器只应该包含业务逻辑。
DOM操作则属于应用程序的表现层逻辑操作,向来以测试难度之高闻名于业界。把任何表现层的逻辑放到控制器中将会大大增加业务逻辑的测试难度。ng提供数据绑定(数据绑定)来实现自动化的DOM操作。如果需要手动进行DOM操作,那么最好将表现层的逻辑封装在指令中。
格式化输入:使用angular表单控件代替
过滤输出:使用angular过滤器代替
在控制器间复用有状态或无状态的代码:使用angular服务代替
管理其它部件的生命周期(如手动创建service实例)

无论对于何种语言,单一职责一直都是写出漂亮代码的首要原则之一,下一节,我们来了解AngularJS的服务及依赖注入。

参考资料:

1.http://www.ngnice.com/docs/guide/controller

开始!AngularJS!(三)- 深入作用域

| Comments

什么是作用域?它做了些什么事情?

作用域是一个存储应用数据模型的对象
没错,它是概念也是一个对象

作用域为表达式提供了一个执行上下文
作用域为表达式提供执行环境,比如像表达式{ {name} },必须在一个拥有该属性的作用域中才能执行

作用域的层级结构对应于DOM树结构
一个应用可以有多个作用域,当新作用域被创建的时候,他们会被当成子作用域添加到父作用域下,这使得作用域会变成一个和相应DOM结构一个的树状结构。

作用域可以监听表达式的变化并传播事件
作用域提供了($watch)方法监听数据模型的变化
作用域提供了($apply)方法把不是由Angular触发的数据模型的改变引入Angular的控制范围内(如控制器,服务,及Angular事件处理器等)

作用域是Web应用的控制器和视图之间的粘结剂
控制器和指令都持有作用域的引用,于是我们可以这样理解它们之间的传递关系:
控制器 –> 作用域 –> 视图(DOM)
指令 –> 作用域 –> 视图(DOM)

何时会产生一个作用域?

于是,你肯定会想要知道,一个作用域的范围是什么?即何时会产生一个作用域?

1.创建控制器时会产生作用域(这个很明显,控制器的构造函数会传入$scope对象)
2.某些指令也会产生作用域

作用域层级

一般情况下,当新的作用域被创建时,它是以子级的形式被创建,嵌入在当前父级作用域,这样就形成了与其所关联的DOM树相对应的一个作用域的树结构。

继承

作用域中定义的属性对于所有子作用域都是可见的,只要子作用域没有定义同名属性。

换句话说,当AngularJS执行表达式{ {name} },它首先会在与当前节点相关的作用域中查找叫做name的属性。如果没找到,则继续向上层作用域搜索,直到根作用域$rootScope。在Javascript中,这被称为原型类型的继承,子作用域以原型的形式继承自父作用域。

上面引入了一个新的概念和对象根作用域$rootScope,正如上面所介绍的,作用域的结构对应于DOM结构,那么最顶层,就和DOM树有根节点一样,每个Angular应用有且仅有一个 根作用域$rootScope。

我们来看个一个具体的例子,贯穿一下上面所提到的概念:

1
2
3
4
5
6
7
8
<body ng-app="listApp">
    <div ng-controller="listController">
        my name is <input type="text" ng-model="name"/>
        <ul ng-repeat="friend in friends">
            <li>Hi { {friend} }, { {introduce()} }</li>
        </ul>
    </div>
</body>
1
2
3
4
5
6
7
8
angular.module("listApp", [])

.controller("listController", function ($scope) {
    $scope.friends = ["Lily","Kate","Mike"];
    $scope.introduce = function() {
        return "this is " + $scope.name + ".";
    };
});

在jsfiddle中查看,http://jsfiddle.net/benweizhu/wLufL8p1/1/

在这个例子中,你可能会看到和上一节定义控制器的方式不同。没错,上一节已经说过,以直接定义函数的方式定义控制器并不是常用的方式,只是为了让你知道控制器的本质是函数。

这里你不用关心定义控制器的方式,之后的章节会讲解。你需要知道的是,这里定义了一个app叫做listApp,里面定义了一个控制器listController。在这个控制器中定义了两个模型(Model)对象name,数组friends和一个函数introduce(),在模板(HTML)上使用了ng-repeat指令来迭代数组friends里面的结果,通过表达式来解析遍历结果和调用函数。

在控制器中初始化了friends,映射了这样的传递关系:控制器 –> 作用域 –> 视图(DOM)

有一点要注意的,指令ng-repeat在执行时,会在每一次遍历都创建一个名字是friend的变量,但是你从结果中看到,页面上friend的值并没有重复。那是因为,AngularJS将每个friend都放在了不同的作用域中,这就是上面提到的,某些指令会产生作用域。

于是乎,在这个app中,就不止一个作用域了,有四个作用域,其中三个作用域是同级的,它们都嵌入在父作用域listController。根据继承的关系,父作用域中的属性对子作用域是可见的,所以{ {introduce()} }表达式可以调用函数introduce。

如果你希望更深入的了解作用域的层级和产生,可以运用Chrome的插件Batarang,它可以帮助你分析AngularJS层级结构。

参考资料:

1.《精通AngularJS》
2.http://www.ngnice.com/docs/guide/scope
3.http://www.angularjs.cn/A00y