AngularJS 新手指南2

过滤

Controller

没作任何变化

Template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="container-fluid">
<div class="row-fluid">
<div class="span2">
Search: <input ng-model="query">
</div>
<div class="span10">
<ul class="phones">
<li ng-repeat="phone in phones | filter:query">
\{\{phone.name}}
<p>\{\{phone.snippet}}</p>
</li>
</ul>
</div>
</div>
</div>

加个标准的 <input> 标签,使用 Angular 的 $filter 函数为 ngRepeat 指令处理输入内容。

原理说明如下:

  • 数据绑定:Angular 的核心特色之一。页面加载时,Angular 把输入框的名字和数据模型的同名变量绑定,保持两者同步。
    用户在输入框输入内容(取名 query),立即作为手机列表的过滤器(phone in phones | filter:query

  • 过滤器(filter )的使用:filter 函数利用 query 值创建一个匹配 query 结果的新数组。
    ngRepeat 自动更新视图,通过过滤器改变手机的数量。

    实验:

  1. 在 index.html 模版增加一个 {{query}} 绑定,可以显示当前的 query 模型的值。

  2. query 模型值出现在 HTML 页面的 title 会是什么情况:

1
<title>Google Phone Gallery: &#123;&#123;query}}</title>

修改<title> 标签,把 ngController 定义放到 HTML 元素上。确保移除原来 body 上的定义。

ngBindngBindTemplate 命令,与双大括号等价的写法:

1
<title ng-bind-template="Google Phone Gallery: \{\{query}}">Google Phone Gallery</title>

双向数据绑定

模版增加了下拉菜单:

1
2
3
4
5
6
7
8
9
10
11
12
13
Search: <input ng-model="query">
Sort by:
<select ng-model="orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
<ul class="phones">
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp">
&#123;&#123;phone.name}}
<p>&#123;&#123;phone.snippet}}</p>
</li>
</ul>

添加了 <select> 元素,命名 orderProp。

我们把 orderBy 跟过滤器关联起来,进一步筛选信息。orderBy 把输入信息拷贝,重新排序并返回。

Angular 在 select 元素和 orderProp 模型间建立了双向数据绑定。orderProp 作为输入的 orderBy 过滤器。

当模型改变(例如用户改变了下拉菜单),Angular 的数据绑定促使视图自动更新,并不需要 DOM 操作代码。

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function PhoneListCtrl($scope) {
$scope.phones = [
{"name": "Nexus S",
"snippet": "Fast just got faster with Nexus S.",
"age": 0},
{"name": "Motorola XOOM™ with Wi-Fi",
"snippet": "The Next, Next Generation tablet.",
"age": 1},
{"name": "MOTOROLA XOOM™",
"snippet": "The Next, Next Generation tablet.",
"age": 2}
];
$scope.orderProp = 'age';
}

我们修改了手机数据模型,增加了 age 属性,此属性用于手机排序。

我们增加了一行代码,设置 orderProp 的默认值为 age。如果不定义默认值,直到用户选择了下拉菜单,模型才会初始化。

解释双向绑定

当应用在浏览器加载时,下拉菜单“Newest”选中,因为我们在 controller 设置了 orderProp 为 age,因此模型到 UI 实现了绑定。当你选择“Alphabetically”项时,模型更新,手机也重新排序。此时相反的方向实现了数据绑定 —— 从 UI 到模型。

实验

  • 在 PhoneListCtrl controller 里,移除 orderProp 默认值声明,Angular 向下拉列表临时增加“未知”选项,并且默认顺序是未排序/自然顺序。

  • 在 index.html 模版增加 {{orderProp}} 绑定,可以显示当前的值。

XHR 和依赖注入

刚才的数据源都是硬编码的数据集,让我们用下 angular 的内建服务 $http 从服务器获取大一点的数据集。我们将用 angular 的依赖注入(DI)为 PhoneListCtrl 控制器服务。

数据

项目里的 app/phones/phones.json 文件包含比较大的手机列表。

Controller

我们将在控制器里用 $http 服务向你的 web 服务器发起一个 HTTP 请求,获取 app/phones/phones.json 文件里的数据。$http 仅是 angular 内建服务 的一种,用来处理常见的操作。

该服务被 angular 的 DI 子系统 管理。依赖注入帮助 web app 保持良好结构(比如:独立的组件用于表现,数据和控制),并且松散耦合(组件之间的依赖性不取决于组件本身,而取决于 DI 子系统)。

1
2
3
4
5
6
7
8
9
function PhoneListCtrl($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}
//PhoneListCtrl.$inject = ['$scope', '$http'];

$http 向我们服务器发起一个 HTTP GET 请求,请求 phones/phones.json (url 相对于 index.html 文件)。服务器响应提供 json 文件的数据。(响应也有可能被后端服务器动态生成,对浏览器和我们的应用而言,似乎是一样的,此处简单起见用了 json 文件。)

$http 服务返回包含 success 方法的约定对象 。我们调用此方法,处理异步响应,并且把手机数据分配到作用域,作为名叫 phones 的模型。注意到 angular 检测到 json 响应,并解析它。

使用一项服务,只需简单定义依赖的名字,作为控制器的构造函数的参数:

1
function PhoneListCtrl($scope, $http) {...}

注意参数的名字只是个象征,因为注入器用它查找相关性。

‘$’ 前缀命名约定

你可以创建自己的服务,实际上 步骤11 这么做了。作为命名约定,angular 内建服务,作用域方法和一些 angular API 会有 ‘$’ 前缀。当命名自己的服务和模型时,为了避免命名冲突,不要使用 ‘$’ 前缀。

注意代码压缩问题

如果你为 PhoneListCtrl 控制器压缩 (http://en.wikipedia.org/wiki/Minification_(programming)) JavaScript 代码,它所有的函数参数也被最小化,依赖注入将无法正确识别服务。

克服最小化代码问题,仅需为 $inject 属性分配一个包含服务标示符的数组,正如(注释)最后一行的代码段:

1
PhoneListCtrl.$inject = ['$scope', '$http'];

另一种解决压缩代码问题的方法,使用方括号:

1
var PhoneListCtrl = ['$scope', '$http', function($scope, $http) { /* constructor body */ }];

实验

  • 在 index.html 底部增加 {{phones | json}} 绑定,看看电话数据 json 格式。

  • 在 PhoneListCtrl 控制器,通过限制手机的数量,预处理 http 响应:

1
$scope.phones = data.splice(0, 5);