多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
在普通的应用中,我们通过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> ~~~