Improve this Doc

In this step, you will add a clickable phone image swapper to the phone details page.

Controller

app/js/controllers.js:

...
var phonecatControllers = angular.module('phonecatControllers',[]);

phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$http',
  function($scope, $routeParams, $http) {
    $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
      $scope.phone = data;
      $scope.mainImageUrl = data.images[0];
    });

    $scope.setImage = function(imageUrl) {
      $scope.mainImageUrl = imageUrl;
    };
  }]);

In the PhoneDetailCtrl controller, we created the mainImageUrl model property and set its default value to the first phone image URL.

We also created a setImage event handler function that will change the value of mainImageUrl.

Template

app/partials/phone-detail.html:

<img ng-src="{{mainImageUrl}}" class="phone">

...

<ul class="phone-thumbs">
  <li ng-repeat="img in phone.images">
    <img ng-src="{{img}}" ng-click="setImage(img)">
  </li>
</ul>
...

We bound the ngSrc directive of the large image to the mainImageUrl property.

We also registered an ngClick handler with thumbnail images. When a user clicks on one of the thumbnail images, the handler will use the setImage event handler function to change the value of the mainImageUrl property to the URL of the thumbnail image.

TODO!

Test

To verify this new feature, we added two end-to-end tests. One verifies that the main image is set to the first phone image by default. The second test clicks on several thumbnail images and verifies that the main image changed appropriately.

test/e2e/scenarios.js:

...
  describe('Phone detail view', function() {

...

    it('should display the first phone image as the main phone image', function() {
      expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
    });


    it('should swap main image if a thumbnail image is clicked on', function() {
      element(by.css('.phone-thumbs li:nth-child(3) img')).click();
      expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/);

      element(by.css('.phone-thumbs li:nth-child(1) img')).click();
      expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
    });
  });

You can now rerun npm run protractor to see the tests run.

You also have to refactor one of your unit tests because of the addition of the mainImageUrl model property to the PhoneDetailCtrl controller. Below, we create the function xyzPhoneData which returns the appropriate json with the images attribute in order to get the test to pass.

test/unit/controllersSpec.js:

...
  beforeEach(module('phonecatApp'));

...

 describe('PhoneDetailCtrl', function(){
    var scope, $httpBackend, ctrl,
        xyzPhoneData = function() {
          return {
            name: 'phone xyz',
            images: ['image/url1.png', 'image/url2.png']
          }
        };


    beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {
      $httpBackend = _$httpBackend_;
      $httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData());

      $routeParams.phoneId = 'xyz';
      scope = $rootScope.$new();
      ctrl = $controller('PhoneDetailCtrl', {$scope: scope});
    }));


    it('should fetch phone detail', function() {
      expect(scope.phone).toBeUndefined();
      $httpBackend.flush();

      expect(scope.phone).toEqual(xyzPhoneData());
    });
  });

Your unit tests should now be passing.

Experiments

TODO! The controller methods are inherited between controllers/scopes, so you can use the same snippet in the phone-list.html template as well. * Move the hello method from PhoneCatCtrl to PhoneListCtrl and you'll see that the button declared in index.html will stop working, while the one declared in the phone-list.html template remains operational.

Summary

With the phone image swapper in place, we're ready for step 11 to learn an even better way to fetch data.