其实每次看到这些代码的时候,我就迫不及待的想知道:什么时候才能把它们改成动态获取的。
~~~
this.phones = [{
name: 'Nexus S',
snippet: 'Fast just got faster with Nexus S.',
age: 1
}, {
name: 'Motorola XOOM™ with Wi-Fi',
snippet: 'The Next, Next Generation tablet.',
age: 2
}, {
name: 'MOTOROLA XOOM™',
snippet: 'The Next, Next Generation tablet.',
age: 3
}];
~~~
很简单的道理,我们的应用上线后,总不能靠改源代码来新增或是删除手机信息吧。本节中,我们将使用一个叫做`$http`的服务,来实现数据的动态获取。
#XHR
我们先看一下什么是XHR:
XMLHttpRequest是一个浏览器接口,使得Javascript可以进行HTTP(S)通信。 最早,微软在IE5引进了这个接口。 因为它太有用,其他浏览器也模仿部署了,ajax操作因此得以诞生。
如果以前我们接触过jquery,相信你对XHR已经不再陌生。
没有XHR以前,我们的浏览器向服务器发送数据的话,只能重新触发一个新的URL,比如我们在form标签中写入action的地址,然后在点击submit按钮时,就会将form中的数据提交到action规定好的地址上。
有了XHR,我们可以做到在不刷新页面的前提下,使用JS在后面偷偷的向服务器传送数据了。当然了,更重要的,这使我们的浏览器真正的在不刷新页面的前提下动了起来。
html5中,XHR已经发展到XMLHttpRequest Level 2,我们可以阅读这篇文章来对XHR有个更深入的认识: http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html
认识了XHR后,我们共同学习下`angularjs`为我们封装好的XHR ---- `$http`。
# $http
前面,我们说$http可以在后台请求数据。那么这句话应该是基于以下前提的:服务器上需要存在数据。
## 数据准备
在这,我们已经准备好了本节需要的数据:
你需要打开:http://guide.mengyunzhi.com/angular-phonecat/app/phones/ ,然后找到里面的`phones.json`,将其下载(点击打开,全选,复制,再粘贴到自己的文件中)后,保存至当前程序的根目录下的`phones`文件夹中。
最终,你的目录结构应该是这样的:
~~~
├── app.moudle.js
├── bower_components
│ ├── angular
│ ├── bootstrap
│ └── jquery
├── index.html
├── phones
│ └── phones.json
├── test.html
└── yun-zhi
├── hello-yunzhi.component.js
├── hello-yunzhi.template.html
├── phone-list.component.js
├── phone-list.template.html
└── yun-zhi.module.js
~~~
TODO:当然了,你还可以使用以下的GIT命令来查看我们为你准备好的代码,并将其直接复制到文件夹中。
## 请求数据
请求数据以前,我们需要简单的讲一下什么是依赖注入(DI Dependency Injection)。在学习ThinkPHP的时候,我们学过命名空间。
比如以下Hello类
~~~
<?php
namespace app\yunzhi;
class Hello {
}
~~~
我们如果在自己的类中去实例化Hello,需要先use,然后才可以直接NEW。
~~~
<?php
....
use app\yunzhi\Hello;
....
public function index()
{
$Hello = new Hello();
}
~~~
否则。ThinkPHP会告诉我们,找不到这个`Hello`。
这样做的好处是什么呢?
我们虽然规定了很多类,但是ThinkPHP只有在使用到他们的时候,才将它们加载到内存,从而提升了运行的效率。当然了,有了命名空间后,我们再也不用担心类名重复的问题了。
angularjs使用一种叫做依赖注入的方法来解决这个问题,其实我们可以简单的理解为是use在angularjs中的表现形式而已。
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: function PhoneListController($http) {
console.dir($http);
this.orderProp = 'age';
}
});
~~~
没错,`$http`做为`controller`的参数传入后,我们就可以在方法体中使用这个`$http`了。
我们在控制台中,看看它的庐山真面目。
![](https://box.kancloud.cn/2016-07-26_5796fd12785bf.png)
没错,正如你看到的,它是一个`function`,有人说老师它怎么是个`function`呢,为什么不是个`object`呢?这是由于在`javascript`中,对象与我们以前学习的有所区别。以前的其它的面向对象的语言中,我们一般先去建立一个`class`,然后实例化这个`class`后,得到一个对象。但javascript没有`class`这个东西。并不是说它不够强大,因为`function`已经完全可以替代`class`。总之,我们需要记住的是。如果我们看到的是一个object,那么这个object,并不是基于哪个类创建的。如果我们看到的返回值是一个function,那么你可以暂时认为它是一个对象。
当然了,你也可以使用下面的代码,再简单的测试一下,它可以有助于你的理解。
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: function PhoneListController($http) {
// 实例化一个对象。
var a = new Object();
console.log(a);
// 实例化一个对象的另一种方法
var b = {};
console.log(b);
// 定义一个函数(这个才是以前我们理解的类)
var man = function(name){
var self = {}; // 定义一个对象,用来给它添加属性
self.name = name, // 属性1
self.sex = 1, // 属性2
self.sayHello = function(){ // 方法1
alert('hello' + self.name);
}
self.helloYunzhi = function(){
console.log('hello yunzhi');
}
return self;
};
// 实例化前面我们定义好的函数man
var zhangsan = new man('zhangsan');
console.dir(zhangsan);
// 调用方法
zhangsan.sayHello();
zhangsan.helloYunzhi();
console.dir($http);
this.orderProp = 'age';
}
});
~~~
测试:
![](https://box.kancloud.cn/2016-07-26_5796fd129319b.png)
有点晕是吧,没关系。反正知道它是一个function就可以了。这个function中,有属性还有小function。我们可以调用它的属性,也可以调用它的小function。
## 调用$http
前面,我们发现`$http`中有个属性是`get`,这个属性的类型是`function`,这个`function`接收两个参数,分别为`url`和`config`。
简单猜一下,应该可以知道:用get的方式向url请求数据,config是本次请求数据的配置信息。
> 官方地址:https://docs.angularjs.org/api/ng/service/$http , https://docs.angularjs.org/api/ng/service/$http#get
![](https://box.kancloud.cn/2016-07-26_5796fd12b0f64.png)
原来config是个选填项,那我们暂时不写。 返回值是个HttpPromise的家伙。
1
`$http.get(url);`
2
~~~
var url = 'phones/phones.json';
var HttpPromise = $http.get(url);
console.dir(HttpPromise);
~~~
![](https://box.kancloud.cn/2016-07-26_5796fd12d1fca.png)
原来返回的类型是Promise,里面有两个方法供我们使用,一个是success,另一个是error。
> 其实Promise是为了解决同步链式操作的一个特定的类型,我们以后会深入的学习它。
下面,我们分别使用这个两个方法,来定义数据正确与错误传输。
~~~
var HttpPromise = $http.get(url);
HttpPromise.success(function(response){});
HttpPromise.error(function(response){});
~~~
~~~
var HttpPromise = $http.get(url);
HttpPromise.success(function(response){
console.log(response);
});
HttpPromise.error(function(response){
// 返回错误码,或是超时触发。
});
~~~
![](https://box.kancloud.cn/2016-07-26_5796fd12e9b6b.png)
没错,成功请求后,数据被传入success方法的第一个参数中。
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: function PhoneListController($http) {
var url = 'phones/phones.json';
var HttpPromise = $http.get(url);
HttpPromise.success(function(response){
console.log(response);
});
HttpPromise.error(function(response){
// 返回错误码,或是超时时触发。
});
this.orderProp = 'age';
}
});
~~~
### then()
实际使用中,我们还可以使用Promise的then方法。这个方位位于Promise的父类中,用JS的说法,是位于其原型链上。
![](https://box.kancloud.cn/2016-07-26_5796fd130bd61.png)
下面,我们使用then方法进行改写:
`$http.get(url).then(function(){}, function(){});`
我们给它传两个参数,第一个参数:请求成功后执行的function。第二个参数:请求失败后执行的function。
~~~
$http.get(url).then(
function(response){
},
function(response){
}
);
~~~
发现了吧,其实上面两种写法只是断行不行,代码没有任何的区别。所以以后当我们再看到这种写法的时候,需要心里很清楚:function(){}作为一个对数传进去了,这个function将会被执行。
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: function PhoneListController($http) {
var url = 'phones/phones.json';
$http.get(url).then(
function(response){
console.log(response);
},
function(response){
// 失败的时候被执行,在此省略
}
);
this.orderProp = 'age';
}
});
~~~
![](https://box.kancloud.cn/2016-07-26_5796fd1325bbc.png)
此时,我们发现,和直接使用success方法不同,then方法的response中,不但返回了数据,还返回状态码等信息。当然了,此时如果我们想获取数据,那么需要使用`reponse.data`,以后,我们将更多的使用then方法。
## 赋值
数据取回来了,下一步,我们将其传给这个controller。
我们把then()中失败执行的代码删除掉后:
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: function PhoneListController($http) {
var url = 'phones/phones.json';
$http.get(url).then(
function(response){
this.phones = response.data;
}
);
this.orderProp = 'age';
}
});
~~~
测试:
![](https://box.kancloud.cn/2016-07-26_5796fd133ea05.png)
发现竟然没有生成任何数据。
这是由于this的作用域不同导致的。
我们再简单测试一下:
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: function PhoneListController($http) {
this.name = 'father';
var url = 'phones/phones.json';
$http.get(url).then(
function(response){
this.phones = response.data;
this.name = 'son';
console.log('son:' + this.name);
}
);
console.log('father: ' + this.name);
this.orderProp = 'age';
}
});
~~~
可能我们期待的结果是:
son: son
father: son
但实际上却是这样:
![](https://box.kancloud.cn/2016-07-26_5796fd135a8e1.png)
发现我们认为的子函数中,没有将name覆盖为son,而且执行顺序也和我们想的完全不一致。至于执行顺序,这是由于js异步处理的原因。在这里我更想说的是,在$http.get()中的this.name,并不是我们的子函数,也是function参数中的一部分,并不是我们心里想的this。
鉴于此,我们进行如下改写:
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: function PhoneListController($http) {
var self = this;
var url = 'phones/phones.json';
$http.get(url).then(
function(response){
self.phones = response.data;
}
);
this.orderProp = 'age';
}
});
~~~
测试:
![](https://box.kancloud.cn/2016-07-26_5796fd1371913.png)
没错,这才是我们想要的。
## 为压缩代码做准备
我们在进行开发时,往往会看到两个js版本,一个是.js,另一个是.min.js。前面的看着很方便,但代码体积大。后面的看着不方便,但代码体积小。一个是人能看的懂的,另一个是机器能看的懂的。在正式的生产环境下,项目上线前我们会对js代码进行压缩。
那么都是什么被压缩了呢?
1、删除空格
2、删除注释
3、删除换行
4、将变量名变短
比如:
~~~
var yunzhi = function(){
var self = {};
self.name = 'yunzhidreamer';
return self;
};
var zhangsan = new Yunzhi();
name = zhangsan.name;
~~~
经过四步压缩后,会变成这个样子。
~~~
var yunzhi=function(){var a={};return a.name="yunzhidreamer",a},zhangsan=new Yunzhi;name=zhangsan.name;
//# sourceMappingURL=test.min.js.map
~~~
那如果参数中包含$http呢? 我们共同来测试一下:
~~~
var yunzhi = function($http){
var self = {};
self.http = $http;
self.name = 'yunzhidreamer';
return self;
};
var http = new Object();
var zhangsan = new Yunzhi();
name = zhangsan.name;
~~~
压缩后:
~~~
var yunzhi=function(a){var b={};return b.http=a,b.name="yunzhidreamer",b},http=new Object,zhangsan=new Yunzhi;name=zhangsan.name;
//# sourceMappingURL=test.min.js.map
~~~
没错,`$http`会做为一个普通的参数被压缩。但是angularjs却只认识$http,比如:
我们的`phone-list.component.js`压缩前:
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: function PhoneListController($http) {
var self = this;
var url = 'phones/phones.json';
$http.get(url).then(
function(response){
self.phones = response.data;
}
);
this.orderProp = 'age';
}
});
~~~
压缩后:
~~~
angular.module("yunZhi").component("phoneList",{templateUrl:"yun-zhi/phone-list.template.html",controller:function(a){var b=this,c="phones/phones.json";a.get(c).then(function(a){b.phones=a.data}),this.orderProp="age"}});
//# sourceMappingURL=test.min.js.map
~~~
此时,我们使用压缩后的代码,就会报一个这样错误的信息:
![](https://box.kancloud.cn/2016-07-26_5796fd138c459.png)
是的,由于$http变成了a,angularjs又不认识这个a,所以报错了。
但我们发现,像angular.module这个字眼,却没有被压缩。这是由于压缩工具很聪明的知道类还类的属性,以及做为字符串传入的值,是不能随便压缩的。基于此,我们对现有的代码进行改写,让压缩对我们不造成影响。
我们使用定义controller的第二种写法 `controller: []`
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: ['$http',
function PhoneListController($http) {
var self = this;
self.orderProp = 'age';
$http.get('phones/phones.json').then(function(response) {
self.phones = response.data;
});
}
]
});
~~~
上面的写法等同于下面这样:
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: ['$http',
function PhoneListController(param1) {
var self = this;
self.orderProp = 'age';
param1.get('phones/phones.json').then(function(response) {
self.phones = response.data;
});
}
]
});
~~~
是的,在实际的执行中,param1会被做为$http来执行。所以即使压缩后的代码是这个样子:`controller:["$http",function(a){`也不会影响到程序的正确执行了。
前台这门学问,真的是越学感觉它越伟大,angularjs当然也是一样。
- 前言
- 第一章:准备知识
- 第一节: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
- 第十节:完善手机详情页
- 第十一节:自定义过滤器
- 第十二节:行为处理
- 第十三节:封装请求
- 第十四节:应用动画
- 第十五节:总结
- 第三章:菜谱管理示例
- 第四章:总结