在普通的应用中,我们通过GET方法来获取查询数据,使用POST方法来提前数据。在angularjs的世界里,我们使用了内部封装$http来获取我们的信息。它的大概的代码如下:
`$http.get(url).then(function(response){...})`
每次请求都需要指定URL。有没有一种方法,可以使我们负责phone-detail.component.js的团队成员可以不必关心实际触发的是哪个URL呢?
从另一个层面说,有没有更简洁的语法可以替代$http来完成数据请求呢?
答案当然是有的。
* * * * *
**扩展信息:**
具象状态传输(英文:Representational State Transfer,简称REST)
> https://zh.wikipedia.org/wiki/REST
其实,我们早早的就接触了rest ,应该在以前我们所接触的应用中,我们就是这种架构。如果你和我们共同学习了ThinkPHP5入门实例教程的话,相信对CURD一定并不陌生。
C:创建数据 ,我们使用post的方法进行数据的创建.
U:更新数据,我们使用post的方法进行数据的创建.
R:读取数据,我们使用get的方法来进行数据的读取.
D:删除数据,我们使用get的方法进行数据的删除。
比如我们请求第二页信息时,会生成如下的URL:xxx?p=2,如果我们以关键字yunzhi搜索的话,会生成如下的URL xxx?keyword=yunzhi。这些设计都是REST架构。
> 其实你并不需要完全的明白什么是REST架构,特别是当很多资料描述的特别抽象的时候。我们只需要知道每个URL都对应特定的内容就可以了。
在标准的REST架构中,CURD,应该是这样:
C:创建数据 ,我们使用post的方法进行数据的创建.
U:更新数据,我们使用put的方法进行数据的创建.
R:读取数据,我们使用get的方法来进行数据的读取.
D:删除数据,我们使用delete的方法进行数据的删除。
但并不是所有的浏览器都支持put与delete,所以,在实际的使用过程中,我们只会出现get与post。但很明显的是,如果我们使用标准的rest,那么,只需要判断请求方法便可以获知用户当前需要的操作类型。
比如url为 http://example.com/resources/142,如果为get方法,则说明操作为获取当前资源的内容;如果为post方法,则为新增资源;如果为put方法,则为更新资源;如果为delete方法,则为删除资源。
维基百科中为我们做了如下总结:
![](https://box.kancloud.cn/2016-08-04_57a30c459e7d3.png)
我们发现,在相同的URL(资源)的条件下,由于请求方法的不同,服务器做出的响应也不同。
这样做的好处是显而易见的,在客户端:我们通过资源地址与请求方法便可以直接判断出当前代码的需求;在服务端,我们可以根据请求地址与方法,判断客户需求后,做出正确的动作。
**扩展信息结束**
* * * * *
## 无处不在的面向对象
在面向对象的世界时,我们往往更希望这样做。
新增数据:
1.新建一个空对象。
2.为这个新对象赋值。
3.执行这个对象的新增方法。
更新数据
1.获取要更新的对象
2.更新这个对象的值。
3.执行这个对象的更新方法。
删除数据:
1.获取要删除的对象。
2.执行这个对象的删除方法。
## 统一接口方法
在angularjs中,ngResource的出现,可以使我们:使用xxx.put()来更新数据, xxx.delete()来删除数据...。的确,如果可以这样写代码的话,简直太易读了,不是吗?
当然了,我们在这仅仅使用到了CRUD中的R的操作,这时候使用ngResource给我们带来的好处并不明显。虽然是这样,使用ngResourcer后代码量及维护的难度也远远小于$http。
我们再来看下维基百科给出的示例:
![](https://box.kancloud.cn/2016-08-04_57a30c459e7d3.png)
按上面的示例,结合我们现有的项目,我们抽象出如下结论:
1. 在获取手机列表时,资源地址应该为http://127.0.0.1:8080/phones/ 请求的动作为get.
2. 在获取某个手机信息时,资源地址应该为http://127.0.0.1:8080/phones/phoneid 请求的动作的get。
现在的文件地址:
1. 手机列表文件地址为:http://127.0.0.1:8080/phones/phones.json.
2. 某个手机信息的文件地址为: http://127.0.0.1:8080/phones/phoneid.json
目标:
1. 资源地址应该为http://127.0.0.1:8080/phones/ 请求的动作为get时,实际请求的地址为:http://127.0.0.1:8080/phones/phones.json
2. 资源地址应该为http://127.0.0.1:8080/phones/phoneid 请求的动作的get时,实际请求的地址为:http://127.0.0.1:8080/phones/phoneid.json
下面,我们使用ngResource来完成这个目标。
# 下载ngResource
`bower install angular-resource#1.5.7 --save
`
~~~
panjiedeMacBook-Pro:angularjs panjie$ bower install angular-resource#1.5.7 --save
bower cached https://github.com/angular/bower-angular-resource.git#1.5.7
bower validate 1.5.7 against https://github.com/angular/bower-angular-resource.git#1.5.7
~~~
--save,会在安装angular-resource库的同时,将安装的信息写入bower.json中。这样做的好处时,在共享你引用的第三库的时候,我们只需要供享bower.json就可以了,而不需要共享所有的第三方库。
# 引入ngResource
`index.html`
~~~
+ <script src="bower_components/angular-resource/angular-resource.js"></script>
~~~
# 定制服务
前面我们接触了组件,过滤器,本节中,我们将使用angularjs中另一个核心的功能--factory
* * * * *
**选学开始**
在angularjs中,如果我们想定义一个服务,那么可以使用provider, value, constant, service, factory。虽然它们的名字有区别,使用的时候也不完全全相同,但可以确认的一点是:他们都是provider!
那什么是provider呢?从字面意思上来看,是个『提供者』。我们可以理解为是个服务,一个我们可以调用的服务。其实我们虽然没有建立过provider,但是却早早的就使用过很多的provider了。
比如我们找到angular.js的第10624行,就可以见到$HttpProvider, 这个便是我们使用到的$http; 第18610行,还有我们前面使用的$SceProvider。不管是$http,还是$sce,它们都为我们**提供**了一系统的功能供我们使用,可能这就是provider这个名称的由来吧。
**选学结束**
* * * * *
## 注入模块
`core/core.module.js`
~~~
// 定义一个core模块,供模块内的组件使用。
angular.module('core', ['ngResource']);
~~~
## 新建factory
`core/phone/phone.server.js`
有人说,老师不应该是`phone.factory.js`吗?怎么是`phone.server.js`呢?
严格意义上来说service是factory的一种,factory又是是provider的一种。只是输出的格式不一样而已。能用service实现的,必然能够使用factory来实现,能用factory实现在,必须能够使用provider来实现。
我们在这只所以用server做为后缀名,只是想让使用的用户更易懂。所以无论是angularjs的server,还是factory,或是provider,我们统一都使用server做为后缀。
> 这是我见过对provider讲的最好的文章: http://hellobug.github.io/blog/angularjs-providers/
ngResource为我们封装好了5个动作。
![](https://box.kancloud.cn/2016-08-04_57a30c45c4847.png)
get:获取某条记录资源
save: 保存某条记录资源
query: 获取资源列表
remove:delete: 删除某条资源
除此以外,我们还可以自己定义其它的动作类型。
`core/phone/phone.service.js`
~~~
angular.
module('core').
factory('Phone', ['$resource', function($resource) {
// 直接返回一个$resource, 并按资源示例格式给出资源地址。
return $resource('phones/:phoneId.json', {}, {
});
}]);
~~~
`:phoneId`将被替换为传入的变量
## 添加依赖注入
`yun-zhi/yun-zhi.module.js`
~~~
// 定义一个yunZhi模块,供组件使用。
// 注入ngRoute core模块
angular.module('yunZhi', ['ngRoute', 'core']);
~~~
## 修改数据获取方式
`yun-zhi/phone-detail.component.js`
~~~
angular.
module('yunZhi').
component('phoneDetail', {
templateUrl: 'yun-zhi/phone-detail.template.html',
controller: ['$routeParams', 'Phone',
function PhoneListController($routeParams, Phone) {
var self = this;
// 设置大图
self.setImage = function setImage(imgUrl){
self.mainImageUrl = imgUrl;
};
// 打印到控制台
console.dir(Phone);
// 使用$resource获取手机信息
self.phone = Phone.get(
// 传入参数
{phoneId:$routeParams.phoneId}
);
}
]
});
~~~
删除了对原来的$http的依赖,增加了对'Phone'的依赖(Phone是一个存在于core模块中的provider)。
## 引用JS文件,并测试
`index.html`
~~~
+ <script src="core/phone/phone.service.js"></script>
~~~
打开: http://127.0.0.1:8080/#!/phones/motorola-xoom
![](https://box.kancloud.cn/2016-08-04_57a30c45e0ad6.png)
没有,它的类型就是factory的返回值--Resource
![](https://box.kancloud.cn/2016-08-04_57a30c460a66f.png)
它里面有delte get query remove save方法,还有一个我们此次用不到的bind方法。
我们再打开网络选项卡:
![](https://box.kancloud.cn/2016-08-04_57a30c4629626.png)
发现正确的加载了对应的数据文件。
回顾目标:
+ 资源地址应该为http://127.0.0.1:8080/phones/ 请求的动作为get时,实际请求的地址为:http://127.0.0.1:8080/phones/phones.json ---- 完成
我们看get中有四个参数,我们说另外三个是作用是什么呢?后面两个,我也不知道,但第2个,我可以告诉你。它是回调函数。
下面,我们利用这个回调函数,给左侧的大图进行初始化。
`yun-zhi/phone-detail.component.js`
~~~
self.phone = Phone.get(
// 传入参数
- {phoneId:$routeParams.phoneId}
+ {phoneId:$routeParams.phoneId},
+ function(phone){
+ self.setImage(phone.images[0]);
+ }
);
~~~
测试:
![](https://box.kancloud.cn/2016-08-04_57a30c46403f9.png)
## 修改数据获取方式
`yun-zhi/phone-list.component.js`
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: ['Phone',
function PhoneListController(Phone) {
console.dir(Phone);
// 获取列表数据
this.phones = Phone.query();
// 初始化排序字段
this.orderProp = 'age';
}
]
});
~~~
我们删除了原有的$http, 引入了core模块中的Phone。将尝试使用query()获取整个数据列表;
测试:
![](https://box.kancloud.cn/2016-08-04_57a30c469052b.png)
是的,正如我们所见,在这种配置下:`return $resource('phones/:phoneId.json',`,使用query()方法时,会自动的触发phones.json文件。
结束:resource为了封装的query(),会自动的忽略到变量的信息,然后拼接成一个新的资源地址。
回顾目标:
+ 资源地址应该为http://127.0.0.1:8080/phones/phoneid 请求的动作的get时,实际请求的地址为:http://127.0.0.1:8080/phones/phoneid.json
为了实现这个目标,我们自定义一个动作 `list`
`core/phone/phone.service.js`
~~~
angular.
module('core').
factory('Phone', ['$resource', function($resource) {
return $resource('phones/:phoneId.json', {}, {
list:{
method:'GET',
params:{phoneId: 'phones'},
isArray:true
}
});
}]);
~~~
使用这个动作:
`yun-zhi/phone-list.component.js`
~~~
- this.phones = Phone.query();
+ this.phones = Phone.list();
~~~
测试:
![](https://box.kancloud.cn/2016-08-04_57a30c46aa479.png)
在学习的过程中,我们要尝试去学习官方文档,虽然在前期我们的确会很费劲,但如果有一天你突然的知道官方文档的知识怎么使用的时候。你会感觉到事半功倍。如果我们一直读不懂官方文档,但只能说明,我们距离熟悉这门语言还远的很。
> 官方地址:https://docs.angularjs.org/api/ngResource/service/$resource
我们可以创建一个新的动作,那么当然也可以去重写query()动作。
`core/phone/phone.service.js`
~~~
- list:{ // 动作名
+ query:{ // 动作名
~~~
`yun-zhi/phone-list.component.js`
~~~
- this.phones = Phone.list();
+ this.phones = Phone.query();
~~~
# 重构代码
重构的目的:
1. 代码结构更清晰,更容易被人理解。(永远不要写只有电脑和你能看懂的代码)
2. 分解成更细小的轮子,也便在其它的应用中重复使用造过的轮子。
现在我们的phone.service在core这个模块中。如果我们可以把它拆下来,而且还不影响core,是不是一件非常美好的事情呢?
## 新建core.phone模块
`core/phone/phone.module.js`
~~~
// 新建core.phone模块,命名方法使我们能辨认出它是core的子模块
angular.module('core.phone', ['ngResource']);
~~~
## 修改`core/core.module.js`
~~~
// 定义一个core模块,供模块内的组件使用。
// 依赖 ngResource core.phone
angular.module('core', ['ngResource', 'core.phone']);
~~~
## 修改`core/phone/phone.service.js`
~~~
- module('core').
+ module('core.phone').
~~~
修改后,这个factory就已经属于core.phone模块了.
## 引入js文件
`index.html`
~~~
<script src="core/phone/phone.module.js"></script>
~~~
在引入JS文件时,我们需要注意引用的顺序。
由于`phone.service.js`的factory属于core.phone,所以在引入`phone.service.js`之前,需要先引入`core/phone/phone.module.js`,否则将会出下如下的错误:
![](https://box.kancloud.cn/2016-08-04_57a30c46cf9bb.png)
提示说,没有找到`core.phone`这个模块。如果出现这个错误,必然是有组件、过滤器、provider....被定义在这个模块上了。在定义到这个模块上以前,我们必须保证已经定义了这个模块。JS代码是顺序执行了,也就是说,必须提前引用`core/phone/phone.module.js`
> 由于依赖注入属于惰性加载(不使用不加载),所以即使我们在模块的定义中,依赖了其它模块,也不并提前对其它模块进行引入。
`index.html`
~~~
<!DOCTYPE html>
<html lang="en" ng-app="phonecatApp">
<head>
<head>
<meta charset="UTF-8">
<title>hello</title>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="app.moudle.js"></script>
<script src="app.config.js"></script>
<script src="yun-zhi/yun-zhi.module.js"></script>
<script src="yun-zhi/phone-list.component.js"></script>
<script src="yun-zhi/hello-yunzhi.component.js"></script>
<script src="yun-zhi/phone-detail.component.js"></script>
<script src="core/core.module.js"></script>
<script src="core/checkmark/checkmark.filter.js"></script>
<script src="core/phone/phone.module.js"></script>
<script src="core/phone/phone.service.js"></script>
</head>
<body>
<div ng-view></div>
</body>
</html>
~~~
- 前言
- 第一章:准备知识
- 第一节:GIT
- 第二节:Node.js
- 第三节:http-server
- 第四节:bower
- 第五节:firefox+chrome
- 第二章:官方示例教程
- 第零节:Hello Yunzhier
- 第一节:静态模板
- 第二节:MVC
- 回调函数
- 第三节:组件
- 第四节:重构组件
- 2.4.1 调用组件
- 2.4.2 规划目录结构
- 2.4.3 剥离V层
- 2.4.4 大话测试
- 第五节:循环过滤器
- 第六节:双向数据绑定
- 第七节:XHR与依赖注入
- 第八节:添加缩略图
- 第九节:模拟页面跳转
- 2.9.1 使用bower
- 2.9.2 使用grunt
- 第十节:完善手机详情页
- 第十一节:自定义过滤器
- 第十二节:行为处理
- 第十三节:封装请求
- 第十四节:应用动画
- 第十五节:总结
- 第三章:菜谱管理示例
- 第四章:总结