In this step you will add a feature that allows users to control the order in which the phone list is displayed. Dynamic sorting can be implemented in this way, adding a new model property, integrating it with the iterator, and then allowing the data binding to do the rest.
Please reset the working directory:
git checkout -f step-4
You should find that in addition to the search box, your app has an additional down menu that allows controlling the order of phone arrangement.
The most important differences between Step 3 and Step 4 are listed below. You can see the complete difference in GitHub.
template
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> <li ng-repeat="phone in phones | filter:query | orderBy:orderProp"> {{phone.name}} <p>{{phone.snippet}}</p> </li></ul>We made the following changes in index.html:
First, we added a <select> tag called orderProp so that our users can choose the two sorting methods we provide.
Then, add an orderBy filter after the filter filter to process the data entering the iterator. The orderBy filter takes an array as input, copies a copy, and reorders the copy and outputs it to the iterator.
AngularJS creates a two-way binding between the select element and the orderProp model. Then, orderProp will be used as input to the orderBy filter.
As we said when we discussed data binding and iterators in Step 3, no matter when the data model changes (such as the user selects a different order in the drop-down menu), AngularJS data binding will automatically update the view. No clumsy DOM operation!
Controller
app/js/controllers.js:
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';}We modified the phones model - the mobile phone array - adding an age attribute to each mobile phone record. We will sort the phones according to the age attribute.
We added a line to the controller code to make the default value of orderProp be age. If we do not set the default value, this model will remain uninitialized until our users select an order in the drop-down menu.
Now we should talk about two-way data binding. Note that when the app is loading in the browser, "Newest" is selected in the drop-down menu. This is because we set orderProp to 'age' in the controller. So binding works in the direction from our model to the user interface - that is, the binding of data from the model to the view. Now when you select "Alphabetically" in the drop-down menu, the data model will be updated at the same time and the phone list array will be reordered. At this time, data binding takes effect from another direction - that is, the binding of data from the view to the model.
test
The changes we make can be verified by a unit test or an end-to-end test. Let's first look at unit tests:
test/unit/controllersSpec.js:
describe('PhoneCat controllers', function() { describe('PhoneListCtrl', function(){ var scope, ctrl; beforeEach(function() { scope = {}, ctrl = new PhoneListCtrl(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 that the default value is set correctly.
We use Jasmine's interface to extract the PhoneListCtrl controller into a beforeEach block, which will be shared by all tests in all parent block describe.
Run these unit tests, just like before, execute the ./scripts/test.sh script, and you should see the following output (note: you need to open http://localhost:9876 in your browser and enter strict mode before the test will run!):
Chrome: Runner reset...Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (3.00 ms) Chrome 19.0.1084.36 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms)
Now we turn our attention to end-to-end testing.
test/e2e/scenarios.js:
... it('should be possible to control phone order via the drop down select box', function() { //let's narrow the dataset to make the test assertions shorter input('query').enter('tablet'); expect(repeater('.phones li', 'Phone List').column('phone.name')). toEqual(["Motorola XOOM/u2122 with Wi-Fi", "MOTOROLA XOOM/u2122"]); select('orderProp').option('Alphabetical'); expect(repeater('.phones li', 'Phone List').column('phone.name')). toEqual(["MOTOROLA XOOM/u2122", "Motorola XOOM/u2122 with Wi-Fi"]); });...End-to-end testing verifies that the sorting mechanism of the option box is correct.
You can now refresh your browser and run the end-to-end test again, or you can run it on AngularJS server.
practise
In the PhoneListCtrl controller, delete the statement setting orderProp, and you will see that AngularJS will temporarily add a blank option in the drop-down menu, and the sorting order is the default sort (i.e. unsorted).
Add a `{{orderProp}} binding to the index.html template to display its value in real time.
Summarize
Now you have provided search functionality for your application and tested it in full. Step 5 We will learn about AngularJS's services and how AngularJS uses dependency injection.
The above is a compilation of the information for AngularJS two-way binding. We will continue to add relevant information in the future. Thank you for your support for this website!