读书频道 > web开发 > Javascript > 用AngularJS开发下一代Web应用
使用Module(模块)组织依赖关系
2013-12-03 10:39:09     我来说两句 
收藏    我要投稿   
AngularJS是一款来自Google的前端JS框架,它的核心特性有:MVC、双向数据绑定、指令和语义化标签、模块化工具、依赖注入、HTML模板,以及对常用工具的封装,例如$http、$cookies、$location等。AngularJS框架的体  立即去当当网订购

开发任何一款优秀的应用都会面临一项非常困难的工作,那就是找到一种方式把代码组织在合适的功能范围内。我们已经看过控制器的处理方式,它会提供一块空间,把正确的数据模型和函数暴露给视图模板。但是其他那些用来支撑我们应用的代码应该怎么办呢?有一块最明显的可以放这些代码的地方,那就是控制器中的函数。

这种做法对于小型的应用和例子来说可以工作得很好,我们已经看到过很多这样的例子,但是在真实的应用中,这种做法很快就会使代码变得无法维护。控制器将会变成一个垃圾场,我们要做的所有东西都会倒在里面。它们会非常难以理解,并且非常难以修改。

我们来看模块。它们提供了一种方法,可以用来组织应用中一块功能区域的依赖关系;同时还提供了一种机制,可以自动解析依赖关系(又叫做依赖注入)。一般来说,我们把这些叫做依赖服务,因为它们会负责为应用提供特殊的服务。

例如,如果购物站点中的一个控制器需要从服务器上获取一个商品列表,那么我们就需要某些对象——不妨把它叫做Items——来处理从服务器端获取商品的工作。进而,Items 对象就需要以某种方式与服务器上的数据库进行交互,可以通过XHR或者WebSocket。

如果在没有模块的情况下来做这件事情,那么代码看起来就像下面这样:
function ItemsViewController($scope) {
  // 向服务器发起请求
  …
  // 解析响应并放入Item 对象
  …
  // 把Items 数组设置到$scope 上,这样视图才能够显示它
  ...
}

虽然这样做确实可以运行,但是却存在大量潜在的问题。

如果其他控制器也需要从服务端获取Items ,那么我们只能把这段代码重写一遍。这会给维护工作造成很大负担,因为如果修改了架构或者其他东西,那么我们就必须在很多地方修改同一段代码。

加上其他一些因素,例如服务端认证、解析数据的复杂性等,会使得控制器对象很难划分出一个合理的功能边界,并且代码阅读起来更加困难。

为了对这段代码进行单元测试,我们需要真正启动一个服务器,或者对XMLHttpRequest进行模拟(monkey patch),从而返回一些假数据。真正运行一个服务器会让测试过程变得很慢,配置服务器很痛苦,而且给测试工作带来不可靠性。模拟(monkey patching)路由的方式可以解决运行速度和不可靠测试的问题,但是这意味着,你必须在两次测试之间去掉被模拟对象上的模拟内容,同时它还会带来额外的复杂度和弱点,因为它会强制给你的数据指定一个“线上”形式(并且当这个形式发生变化时需要同时修改测试代码)。

利用模块和模块内置的依赖注入功能,我们就可以把控制器写得更加简单,示例如下:
function ShoppingController($scope, Items) {
  $scope.items = Items.query();
}

现在你可能会问自己,“不错,这样看起来很酷,但是Items 这个对象是从哪儿来的呢?”以上代码假设我们已经把Items 对象定义成了一个服务。

服务都是单例(单个实例)的对象,它们用来执行必要的任务,支撑应用的功能。

Angular内置了很多服务,例如$location服务,用来和浏览器的地址栏进行交互;$route 服务,用来根据URL地址的变化切换视图;还有$http服务,用来和服务器进行交互。

你可以并且也应该去创建自己的服务,用它们来实现你的应用所特有的功能。如果需要,服务可以在任何控制器之间进行共享。因此,当你需要在多个控制器之间进行交互和共享状态时,这些服务就是一种很好的机制。Angular内置的服务以$ 符号打头,你当然可以给你的服务随意起名字,但是,最好不要以$ 打头,以免引起命名冲突。

你可以使用模型对象的API来定义服务。以下3 个函数可以用来创建一般的服务,它们的复杂度和功能不同。

函数 定义
provider(name, Object OR constructor() ) 一个可配置的服务,创建的逻辑比较复杂。如果你传递了一个Object作为参数,那么这个Object对象必须带有一个名为$get的函数,这个函数需要返回服务的名称。否则,Angular 会认为你传递的是一个构造函数,调用构造函数会返回服务实例对象。
factory(name,$get Function() ) 一个不可配置的服务,创建逻辑比较复杂。你需要指定一个函数,当调用这个函数的时候,会返回服务的实例。你可以把它看成provider(name, { $get: $getFunction() } ) 的形式。
service(name, con structor() ) 一个不可配置的服务,创建逻辑比较简单。与上面provider 函数的constructor 参数类似,Angular 调用它可以创建服务实例。

后面我们会解释provider()函数的配置项,我们先来讨论一个针对Item 的使用factory()的例子。我们可以像下面这样来编写服务:
// 创建一个模型用来支撑我们的购物视图
var shoppingModule = angular.module('ShoppingModule', []);
// 设置好服务工厂,用来创建我们的Items 接口,以便访问服务端数据库
shoppingModule.factory('Items', function() {
  var items = {};
  items.query = function() {
    // 在真实的应用中,我们会从服务端拉取这块数据...
    return [
      {title: 'Paint pots', description: 'Pots full of paint', price: 3.95},
      {title: 'Polka dots', description: 'Dots with polka', price: 2.95},
      {title: 'Pebbles', description: 'Just little rocks', price: 6.95}
    ];
  };
  return items;
});

当Angular创建ShoppingController 时,它会把$scope 对象和刚定义的Items服务作为参数传递进去。这一点是通过参数名匹配来实现的,也就是说,Angular会查看我们的ShoppingController 类的函数签名,然后就会发现它需要一个Items对象。既然我们已经把Items 定义成了一个服务,那么Angular当然知道去哪里找这个服务了。以字符串的形式查找这些依赖关系的结果是,可以进行注入的那些函数(例如控制器的构造器)的参数是没有顺序的。所以,除了下面这种写法之外:
function ShoppingController($scope, Items) {...}

你还可以这样写:
function ShoppingController(Items, $scope) {...}

这种写法同样能够按照我们所期望的方式运行。

为了让这一机制能够和模板配合起来,我们需要把模块名称告诉ng-app 指令,示例如下:
<html ng-app='ShoppingModule'>

为了完成这个例子,我们可以把模板的其他部分实现如下:
<body ng-controller="ShoppingController">
  <h1>Shop!</h1>
  <table>
      <td>{{item.title}}</td>
      <td>{{item.description}}</td>
      <td>{{item.price | currency}}</td>
    </tr>
  </table>
</div>

应用最终完成之后的效果如图2-2所示。

 

点击复制链接 与好友分享!回本站首页
分享到: 更多
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:监控多个东西
下一篇:我需要多少个模块呢
相关文章
图文推荐
3.12 本章小结
3.10 添加新函数
3.9 递归
3.8 闭包
排行
热门
文章
下载
读书

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做最好的IT技术学习网站