Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

AngularJS two-way data binding


May 08, 2021 AngularJS


Table of contents


Two-way data binding

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.

  • In addition to the search box, the app displays a drop-down menu that allows the user to control the sorting of the listed phones.

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.

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 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:

  • First, we've added an <select> called orderProp so our users can choose one of the two provided sorting options.
AngularJS two-way data binding
  • We then connect the 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!

Controller

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.

Test

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.

Experiment

  • 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}}

  • Add a - before the sort value to reverse the sort <option value="-age">Oldest</option>

Summarize

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.