May 08, 2021 AngularJS
In this step, you'll add a feature that lets your users control the sorting of items in your phone's list. This dynamic sorting is implemented by creating a new module property, connecting them with an iterator, and letting the data bind to do the rest of the work.
Reset the workspace to step four
git checkout -f step-4
Refresh your browser or check this step online: Step 4 Live Demo
The most important differences between step four and step five are listed below. You can see the full difference in GitHub.
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>
Let's make the
index.html
the index template:
<select>
called
orderProp
so our users can choose one of the two provided sorting options.
filter
filter to
orderBy
filter to further process the input to the iterator.
orderBy
is a filter that uses an input array, copies it, and then returns a copy, reordering the copy.
Angular creates
select
two-way data binding between the select element and the
orderProp
module.
orderProp
then used as input
orderBy
filter.
As we discussed in the third step of this section, with regard to data binding and iterators, whenever a module changes (for example, because the user changes the order by selecting a drop-down menu), Angular's data binding causes the view to update automatically. Non-bloated DOM operating code is necessary!
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';
});
We modified
phones
module - an array of phones - and added
age
property to each phone record.
Properties are used to sort phones by age.
We added a line to the controller to set the default value of
orderProp
to
age
.
If we haven't set a default value here,
orderBy
remains unitialized until our users pick an option from the drop-down menu.
This is a good time to explain two-way data binding. N
ote that when the app is loaded in the browser, The Latest in the drop-down menu is selected. T
his is because we set
orderProp
'age'
T
he same is true of binding in the direction from our module to the UI. N
ow, if you select Alphabetically in the drop-down menu, the module will also be updated and the phone will be reordered.
This is what data binding does in the opposite direction -- from the UI to the module.
The changes we make will be validated in unit tests and end-to-end tests. Let's take a look at unit tests first.
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');
});
});
});
Unit tests now verify the default sort property settings.
We use Jasmins' API to pull the controller architecture out
beforeEach
which is shared by all
describe
in the parent describe block.
By now you should see the following output in the Karma tab:
Chrome 22.0: Executed 2 of 2 SUCCESS (0.021 secs / 0.001 secs)
Let's bring our attention back to end-to-end testing.
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"
]);
});...
The end-to-end test verifies that the sorting mechanism for the selection box is working correctly.
Now you can
npm run protractor
to see the test run.
In
PhoneListCtrl
remove the
orderProp
value, and you'll see that Angel temporarily adds a new blank ("unknown") option to the pull-down list, and the sort will default to out-of-order/natural sorting.
The current value of the index is displayed
index.html
.html the indexprop to the template.
{{orderProp}}
-
before the sort value to reverse the sort
<option value="-age">Oldest</option>
Now that we've added list sorting and tested the app, go to Step 5 XHR and Dependency Injection to learn about TheNgular service and how Angual uses Dependency Injection.