本节书摘来自华章出版社《AngularJS深度剖析与最佳实践》一书中的第2章,第2.4节,作者 雪狼 破狼 彭洪伟,更多章节内容可以访问云栖社区“华章计算机”公众号查看
2.4 控制器
和传统的MVC程序中一样,Angular中的控制器(controller)用来对模块(scope)进行操作,包括初始化数据和定义事件响应函数等。
我们常见的定义控制器的方法是:angular.module('com.ngnice.app').controller ('UserListCtrl', function() {...});,其中:angular.module('com.ngnice.app')语句在前面已经讲过,是返回一个现有的module实例,而Controller就是这个module实例上的一个方法,它的作用是把后面的函数以UserListCtrl为名字,注册到模块中去,以便需要时可以根据名字找到它。
使用控制器的场景有几种。最常见的是用在路由中。
比如在angular-ui-router中,我们可以通过下面的语句使用它:
$stateProvider.state('user.list', {
url: '/list',
templateUrl: 'views/user/list.html',
controller: 'UserListCtrl'
});
这样,当用户访问/user/list这个URL的时候,angular-ui-router插件就会实例化一个名为UserListCtrl的控制器,同时,创建一个scope对象并传给它。这个控制器实例会在这个scope对象上创建属性、方法等,这个过程称为“初始化scope对象”。
初始化完之后,加载模板,并且把scope对象传入,模板中会通过Angular指令绑定这些属性、方法。Angular会通过一个称为摘要循环(digest cycle)的过程,自动维护scope变量和视图中DOM节点的一致性,这时候的DOM我们称之为“活DOM(Live DOM)”。更具体的工作原理我们会在第3章“背后的原理”的3.2节“Angular启动过程”中详细讲解。
另一个常用的场景是在单元测试中。
通常,在单元测试阶段我们不必测试视图,而是将其留待端到端测试阶段。在单元测试阶段,我们要关注的是控制器的工作逻辑。前面我们提过,控制器的作用是在scope对象上创建属性、方法,所以我们的测试逻辑就是看它是否创建了正确的属性,以及由它创建的方法是否能正常工作。
于是我们得出了下列测试逻辑:
var scope = $rootScope.$new();
var ctrl = $controller('UserListCtrl');
ctrl(scope);
// TODO: 检测scope中是否如同预期的产生了初始数据
// TODO: 调用scope中的方法,然后检测scope变量是否发生了预期的变化
其中的$controller是Angular提供的一个系统服务,用来查找以前通过module.controller('UserListCtrl', function() {...});注册的控制器函数。
还有一个不常用但值得提倡的场景是在指令中,特别是用来封装一个界面片段的“组件型指令”,我们在2.6节“指令”中会进一步展开讲解。之所以在这种类型的指令中提倡使用控制器,主要是为了方便进行单元测试,而不用引入对视图层的测试。
有一些第三方服务或指令也会使用控制器,它们所做的工作实际上和angular-ui-router一样的:创建一个scope,找到一个控制器,然后用控制器对scope进行初始化,最后把scope绑定到视图,把生成的Live DOM渲染出来。
掌握了控制器的工作原理,在自己的代码中也可以灵活运用,凡是需要Live DOM的地方都可以通过各种形式使用。
生成Live DOM时涉及另一个重要的知识点$compile,完整的实现方式我会在后面2.6节“指令”中讲解。