本章节中,我们将为之前展示的手机信息列表提供一个排序功能。要想实现动态排序的功能,我们只需要新建一个模型属性,并且把它和循环器绑定在一起,剩下的就交给数据绑定去完成了。
相比上一章中的例子,本章中的例子多添加了一个下拉菜单,它允许用户自己控制手机列表的显示顺序。首先我们把代码切换到step-4:
git checkout -f step-4
刷新浏览器查看效果(假设之前你已经运行了上一章的例子)。你可以在线看效果。
模版
app/index.html
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">
<span>{{phone.name}}</span>
<p>{{phone.snippet}}</p>
</li>
</ul>
代码对index.html主要做了以下几点改动:
- 第一布我们添加一个名为
orderProp
的<select>
标签元素,这样用户就能从提供的两种排序中选择自己想要的排序方式了。
- 第二步我们再之前的
filter
过滤器链中添加一个orderBy
过滤器用来对结果进行排序。orderBy
过滤器的输入为一个数组,对其进行排序并且返回排序后的数组。
这个例子中,Angular在<select>
元素和orderProp
数据模型之间建立双向数据绑定,然后orderBy
过滤器就可以用它作为输入,并且返回结果了。
上一章中我们讨论了关于循环器的数据绑定,只要模型数据发生变化(比如用户下拉排序菜单选择了不通的排序方式),Angular的数据绑定功能将确保页面会同步展示最新结果。其他的手工控制代码都不用自己写。
控制器
app/js/controllers.js:
var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', function ($scope) {
$scope.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}
];
$scope.orderProp = 'age';
});
上述代码主要包含以下改动:
- 首先我们修改了
phones
数据模型,为每条手机记录添加了一个age
属性。页面将支持根据这个属性来排序。 - 另外我们在接下来的一行中把
orderProp
的默认值设成age
(告诉页面第一次载入时按照age
排序)。如果这里没有设置默认值,页面在载入的时候将是未排序过的,直到用户从下拉菜单中选择了一个排序方式。
这个例子能非常好的说明双向绑定
这个概念。当页面载入后,我们可以看到下拉框显示的是"Newest",这是因为我们把orderProp
默认设置为了age
。这是一次从数据模型到UI的单向绑定。另外一方面,当我们选择下拉菜单的"Alphabetical",后台中的手机数组列表就会被重新排列,这就是从UI到数据模型的单向绑定。这两者结合起来,就是数据的双向绑定了。
测试
这次的改动将涉及单元测试和E2E测试两个测试。我们先看以下单元测试:
test/unit/controllersSpec.js:
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
var scope, ctrl;
beforeEach(module('phonecatApp'));
beforeEach(inject(function($controller) {
scope = {};
ctrl = $controller('PhoneListCtrl', {$scope:scope});
}));
it('should create "phones" model with 3 phones', function() {
expect(scope.phones.length).toBe(3);
});
it('should set the default value of orderProp model', function() {
expect(scope.orderProp).toBe('age');
});
});
});
上述单元测试代码将验证orderProp
默认值是否设置成功。
我们使用了Jasmine的API来把控制器的构造过程抽取出来放入了beforeEach
模块中,这将使得它能被父模块describe
中的所有测试所共享。
你将会从Karma的控制台输出中看到以下消息:
Chrome 22.0: Executed 2 of 2 SUCCESS (0.021 secs / 0.001 secs)
下面让我们来看看E2E测试,以下是代码:
test/e2e/scenarios.js
:
...
it('should be possible to control phone order via the drop down select box', function() {
var phoneNameColumn = element.all(by.repeater('phone in phones').column('phone.name'));
var query = element(by.model('query'));
function getNames() {
return phoneNameColumn.map(function(elm) {
return elm.getText();
});
}
query.sendKeys('tablet'); //let's narrow the dataset to make the test assertions shorter
expect(getNames()).toEqual([
"Motorola XOOM\u2122 with Wi-Fi",
"MOTOROLA XOOM\u2122"
]);
element(by.model('orderProp')).element(by.css('option[value="name"]')).click();
expect(getNames()).toEqual([
"MOTOROLA XOOM\u2122",
"Motorola XOOM\u2122 with Wi-Fi"
]);
});...
E2E测试将用来验证下拉选择框的排序功能是否正常。你可以通过npm run protractor
来运行这个测试用例。
实验小能手
- 在
PhoneListCtrl
控制器代码中,把$scope.orderProp = 'age';
这句话注释掉,然后你就会看到Angular会为页面上的下拉框添加了一个新的空白选项(Unknown),并且当前的列表也是未经排序的。 - 添加一个{{orderProp}}数据绑定用来查看当前的排序方式。
- 想要倒排某个排序?很简单,尝试添加一个新的select选项:
<option value="-age">Oldest</option>
。
总结
现在我们已经为网站添加了排序功能,下一章,我们将学习Angular的服务体系和依赖注入。