12. 工具函数
12.1. 上下文绑定
angular.bind 是用来进行上下文绑定,参数动态绑定的工具函数。
1 | var f = angular.bind({a: 'xx'}, |
参数动态绑定:
1 | var f = function(x){console.log(x)} |
12.2. 对象处理
对象复制: angular.copy()
1 | var a = {'x': '123'}; |
对象聚合: angular.extend()
1 | var a = {'x': '123'}; |
空函数: angular.noop()
大小写转换: angular.lowercase() 和 angular.uppercase()
JSON转换: angular.fromJson() 和 angular.toJson()
遍历: angular.forEach() ,支持列表和对象:
1 | var l = {a: '1', b: '2'}; |
12.3. 类型判定
1 | angular.isArray |
13. 其它服务
13.1. 日志
ng 提供 $log
这个服务用于向终端输出相关信息:
1 | error() |
13.2. 缓存
ng 提供了一个简单封装了缓存机制 $cacheFactory
,可以用来作为数据容器:
1 | var TestCtrl = function($scope, $cacheFactory){ |
调用时,第一个参数是 id ,第二个参数是配置项,目前支持 capacity 参数,用以设置缓存能容留的最大条目数。超过这个个数,则自动清除较旧的条目。
缓存实例的方法:
info() 获取 id , size 信息
put(k, v) 设置新条目
get(k) 获取条目
remove(k) 删除条目
removeAll() 删除所有条目
destroy() 删除对本实例的引用$http
的调用当中,有一个 cache 参数,值为 true 时为自动维护的缓存。值也可以设置为一个 cache 实例。
13.3. 计时器
$timeout
服务是 ng 对 window.setTimeout() 的封装,它使用 promise 统一了计时器的回调行为:
1 | angular.module('app', [], angular.noop) |
使用 $timeout.cancel()
可以取消计时器。
13.4. 表达式函数化
$parse
这个服务,为 js 提供了类似于 Python 中 @property
的能力:
1 | angular.module('app', [], angular.noop) |
$parse
返回一个函数,调用这个函数时,可以传两个参数,第一个作用域,第二个是变量集,后者常用于覆盖前者的变量:
1 | var get_name = $parse('name'); |
$parse
返回的函数,也提供了相应的 assign 功能,可以为表达式赋值(如果可以的话):
1 | var get_name = $parse('name'); |
13.5. 模板单独使用
ng 中的模板是很重要,也很强大的一个机制,自然少不了单独运用它的方法。不过,即使是单独使用,也是和 DOM 紧密相关的程度:
定义时必须是有 HTML 标签包裹的,这样才能创建 DOM 节点
渲染时必须传入 $scope
之后使用 $compile
就可以得到一个渲染好的节点对象了。当然, $compile
还要做其它一些工作,指令处理什么的。
1 | angular.module('app', [], angular.noop) |
14. 自定义模块和服务
14.1. 模块和服务的概念与关系
总的来说,模块是组织业务的一个框框,在一个模块当中定义多个服务。当你引入了一个模块的时候,就可以使用这个模块提供的一种或多种服务了。
比如 AngularJS 本身的一个默认模块叫做 ng ,它提供了 $http
, $q
等等服务。
服务只是模块提供的多种机制中的一种,其它的还有命令( directive ),过滤器( filter ),及其它配置信息。
然后在额外的 js 文件中有一个附加的模块叫做 ngResource , 它提供了一个 $resource
服务。
定义时,我们可以在已有的模块中新定义一个服务,也可以先新定义一个模块,然后在新模块中定义新服务。
使用时,模块是需要显式地的声明依赖(引入)关系的,而服务则可以让 ng 自动地做注入,然后直接使用。
14.2. 定义模块
定义模块的方法是使用 angular.module 。调用时声明了对其它模块的依赖,并定义了“初始化”函数。
1 | var my_module = angular.module('MyModule', [], function(){ |
这段代码定义了一个叫做 MyModule 的模块, my_module 这个引用可以在接下来做其它的一些事,比如定义服务。
14.3. 定义服务
服务本身是一个任意的对象。但是 ng 提供服务的过程涉及它的依赖注入机制。在这里呢,就要先介绍一下叫 provider 的东西。
简单来说, provider 是被“注入控制器”使用的一个对象,注入机制通过调用一个 provider 的 $get()
方法,把得到的东西作为参数进行相关调用(比如把得到的服务作为一个 Controller 的参数)。
在这里“服务”的概念就比较不明确,对使用而言,服务仅指 $get()
方法返回的东西,但是在整体机制上,服务又要指提供了 $get()
方法的整个对象。
1 | //这是一个provider |
上面的代码是一种定义服务的方法,当然, ng 还有相关的 shortcut, ng 总有很多 shortcut 。
第一个是 factory 方法,由 $provide
提供, module 的 factory 是一个引用,作用一样。这个方法直接把一个函数当成是一个对象的 $get()
方法,这样你就不用显式地定义一个 provider 了:
1 | var app = angular.module('Demo', [], function($provide){ |
在 module 中使用:
1 | var app = angular.module('Demo', [], function(){ }); |
第二个是 service 方法,也是由 $provide
提供, module 中有对它的同名引用。 service 和 factory 的区别在于,前者是要求提供一个“构造方法”,后者是要求提供 $get()
方法。意思就是,前者一定是得到一个 object ,后者可以是一个数字或字符串。它们的关系大概是:
1 | var app = angular.module('Demo', [], function(){ }); |
这里插一句,js 中 new 的作用,以 new a() 为例,过程相当于:
创建一个空对象 obj
把 obj 绑定到 a 函数的上下文当中(即 a 中的 this 现在指向 obj )
执行 a 函数
返回 obj
service 方法的使用就很简单了:
1 | var app = angular.module('Demo', [], function(){ }); |
14.4. 引入模块并使用服务
结合上面的“定义模块”和“定义服务”,我们可以方便地组织自己的额外代码:
1 | angular.module('MyModule', [], function($provide){ |
15. 附加模块 ngResource
15.1. 使用引入与整体概念
ngResource 这个是 ng 官方提供的一个附加模块。附加的意思就是,如果你打算用它,那么你需要引入一人单独的 js 文件,然后在声明“根模块”时注明依赖的 ngResource 模块,接着就可以使用它提供的 $resource
服务了。完整的过程形如:
1 |
|
$resource
服务,整体上来说,比较像是使用类似 ORM 的方式来包装了 AJAX 调用。区别就是 ORM 是操作数据库,即拼出 SQL 语句之后,作 execute 方法调用。而 $resource
的方式是构造出 AJAX 请求,然后发出请求。同时,AJAX 请求是需要回调处理的,这方面, $resource
的机制可以使你在一些时候省掉回调处理,当然,是否作回调处理在于业务情形及容错需求了。
使用上 $resource
分成了“类”与“实例”这两个层面。一般地,类的方法调用就是直观的调用形式,通常会返回一个对象,这个对象即为“实例”。
“实例”贯穿整个服务的使用过程。“实例”的数据是填充方式,即因为异步关系,回调函数没有执行时,实例已经存在,只是可能它还没有相关数据,回调执行之后,相关数据被填充到实例对象当中。实例的方法一般就是在类方法名前加一个 $
,调用上,根据定义,实例数据可能会做一些自动的参数填充,这点是区别实例与类的调用上的不同。
好吧,上面这些话可能需要在看了接下来的内容之后再回过来理解。
15.2. 基本定义
就像使用 ORM 一般要先定义 Model 一样,使用 $resource
需要先定义“资源”,也就是先定义一些 HTTP 请求。
在业务场景上,我们假设为,我们需要操作“书”这个实体,包括创建create,获取详情read,修改update,删除delete,批量获取multi,共五个操作方法。实体属性有:唯一标识id,标题title,作者author。
我们把这些操作定义成 $resource
的资源:
1 | var app = angular.module('Demo', ['ngResource'], angular.noop); |
定义是使用使用 $resource
这个函数就可以了,它接受三个参数:
url
默认的params(这里的 params 即是 GET 请求的参数,POST 的参数单独叫做“postData”)
方法映射
方法映射是以方法名为 key ,以一个对象为 value ,这个 value 可以有三个成员:
method, 请求方法,’GET’, ‘POST’, ‘PUT’, ‘DELETE’ 这些
params, 默认的 GET 参数
isArray, 返回的数据是不是一个列表
15.3. 基本使用
在定义了资源之后,我们看如果使用这些资源,发出请求:
1 | var book = Book.read({id: '123'}, function(response){ |
这里我们进行 Book 的“类”方法调用。在方法的使用上,根据官方文档:
1 | HTTP GET "class" actions: Resource.action([parameters], [success], [error]) |
我们这里是第二种形式,即类方法的非 GET 请求。我们给的参数会作为 postData 传递。如果我们需要 GET 参数,并且还需要一个错误回调,那么:
1 | var book = Book.read({get: 'haha'}, {id: '123'}, |
调用之后,我们会立即得到的 book ,它是 Book 类的一个实例。这里所谓的实例,实际上就是先把所有的 action 加一个 $
前缀放到一个空对象里,然后把发出的参数填充进去。等请求返回了,把除 action 以外的成员删除掉,再把请求返回的数据填充到这个对象当中。所以,如果我们这样:
1 | var book = Book.read({id: '123'}, function(response){ |
就能看到 book 实例的变化过程了。
现在我们得到一个真实的实例,看一下实例的调用过程:
1 | //响应的数据是 {result: 0, msg: '', obj: {id: 'xxx'}} |
可以看到,在请求回调之后, book 这个实例的成员已经被响应内容填充了。但是这里有一个问题,我们返回的数据,并不适合一个 book 实例。格式先不说,它把 title 和 author 这些信息都丢了(因为响应只返回了 id )。
如果仅仅是格式问题,我们可以通过配置 $http
服务来解决( AJAX 请求都要使用 $http
服务的):
1 | $http.defaults.transformResponse = function(data){return angular.fromJson(data).obj}; |
当然,我们也可以自己来解决一下丢信息的问题:
1 | var p = {title: '测试标题', author: '测试作者'}; |
不过,始终会有一些不方便了。比较正统的方式应该是调节服务器端的响应,让服务器端也具有和前端一样的实例概念,返回的是完整的实例信息。即使这样,你也还要考虑格式的事。
现在我们得到了一个真实的 book 实例了,带有 id 信息。我们尝试一下实例的方法调用,先回过去头看一下那三种调用形式,对于实例只有第三种形式:
non-GET instance actions: instance.$action([parameters], [success], [error])
首先解决一个疑问,如果一个实例是进行一个 GET 的调用会怎么样?没有任何问题,这当然没有任何问题的,形式和上面一样。
如何实例是做 POST 请求的话,从形式上看,我们无法控制请求的 postData ?是的,所有的 POST 请求,其 postData 都会被实例数据自动填充,形式上我们只能控制 params 。
所以,如果是在做修改调用的话:
1 | book.$update({title: '新标题', author: '测试作者'}, function(response){ |
这样是没有意义的并且错误的。因为要修改的数据只是作为 GET 参数传递了,而 postData 传递的数据就是当前实例的数据,并没有任何修改。
正确的做法:
1 | book.title = '新标题' |
显然,这种情况下,回调都可以省了:
1 | book.title = '新标题' |
15.4. 定义和使用时的占位量
两方面。一是在定义时,在其 URL 中可以使用变量引用的形式(类型于定义锚点路由时那样)。第二时定义默认 params ,即 GET 参数时,可以定义为引用 postData 中的某变量。比如我们这样改一下:
1 | var Book = $resource('/book/:id', {}, actions); |
在 URL 中有一个 :id ,表示对 params 中 id 这个变量的引用。因为 read 是一个 POST 请求,根据调用形式,第一个参数是 params ,第二个参数是 postData 。这样的调用结果就是,我们会发一个 POST 请求到如下地址, postData 为空:
/book/123?_method=read
再看默认的 params 中引用 postData 变量的形式:
1 | var Book = $resource('/book', {id: '@id'}, actions); |
这样会出一个 POST 请求, postData 内容中有一个 id 数据,访问的 URL 是:
/book?_method=read&id=123&title=xx
这两个机制也可以联合使用:
1 | var Book = $resource('/book/:id', {id: '@id'}, actions); |
结果就是出一个 POST 请求, postData 内容中有一个 id 数据,访问的 URL 是:
/book/123?_method=read&title=xx
15.5. 实例
ngResource 要举一个实例是比较麻烦的事。因为它必须要一个后端来支持,这里如果我用 Python 写一个简单的后端,估计要让这个后端跑起来对很多人来说都是问题。所以,我在几套公共服务的 API 中纠结考察了一番,最后使用 www.rememberthemilk.com 的 API 来做了一个简单的,可用的例子。
例子见: http://zouyesheng.com/demo/ng-resource-demo.html (可以直接下载看源码)
先说一下 API 的情况。这里的请求调用全是跨域的,所以交互上全部是使用了 JSONP 的形式。 API 的使用有使用签名认证机制,嗯, js 中直接算 md5 是可行的,我用了一个现成的库(但是好像不能处理中文吧)。
这个例子中的 LoginCtrl 大家就不用太关心了,参见官方的文档,走完流程拿到 token 完事。与 ngResource 相关的是 MainCtrl 中的东西。
其实从这个例子中就可以看出,目前 ngResource 的机制对于服务端返回的数据的格式是严重依赖的,同时也可以反映出 $http 对一些场景根本无法应对的局限。所以,我现在的想法是理解 ngResource 的思想,真正需要的人自己使用 jQuery 重新实现一遍也许更好。这应该也花不了多少时间, ngResource 的代码本来不多。
我为什么说 $http
在一些场景中有局限呢。在这个例子当中,所有的请求都需要带一个签名,签名值是由请求中带的参数根据规则使用 md5 方法计算出的值。我找不到一个 hook 可以让我在请求出去之前修改这个请求(添加上签名)。所以在这个例子当中,我的做法是根据 ngResource 的请求最后会使用 $httpBackend
这个底层服务,在 module 定义时我自己复制官方的相关代码,重新定义 $httpBackend
服务,在需要的地方做我自己的修改:
script.src = sign_url(url);
不错,我就改了这一句,但我不得不复制了 50 行官方源码到我的例子中。
另外一个需要说的是对返回数据的处理。因为 ngResource 会使用返回的数据直接填充实例,所以这个数据格式就很重要。
首先,我们可以使用 $http.defaults.transformResponse
来统一处理一下返回的数据,但是这并不能解决所有问题,可目前 ngResource 并不提供对每一个 action 的单独的后处理回调函数项。除非你的服务端是经过专门的适应性设计的,否则你用 ngResource 不可能爽。例子中,我为了获取当前列表的结果,我不得不自己去封装结果:
1 | var list_list = List.getList(function(){ |
关注 web翎云阁,定时推送,互动精彩多,若你有更好的见解,欢迎留言探讨!