Обработчики событий в AngularJS

На этом шаге мы сделаем на странице деталей телефона отображение крупного изображения по клику на его миниатюру.

Переключим проект на состояние соответствующее даному шагу:

git checkout -f step-10

На этом шаге были внесены такие изменения.

Для начала рассмотрим простой пример вызова функции. К примеру добавим новый метод в контроллер PhoneDetailCtrl:
$scope.hello = function(name) {
    alert('Hello ' + (name || 'world') + '!');
}

и добавим в файл phone-detail.html:
<button ng-click="hello('Elmo')">Hello</button>


Контроллер

...
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;
    };
  }]);

В контроллере PhoneDetailCtrl мы создали свойство модели данных mainImageUrl и по умолчанию установили ему значение первого изображения из всех доступных.

Мы также создали обработчик событий setImage, который будет изменять значение свойства mainImageUrl.

Шаблон

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

С помощью директивы ngSrc мы связали большое изображение со свойством mainImageUrl.

Мы также зарегистрировали обработчик ngClick для миниатюрных изображений. Когда пользователь кликает по миниатюре обработчик вызывает функцию setImage, чтобы изменить значение свойства mainImageUrl.

Тестирование

Что проверить новую функциональность, мы добавили два end-to-end теста. Первый проверяет что большому изображению по умолчанию устанавливается первая картинка. Второй тест кликает по некоторым миниатюрам и проверяет, что большое изображение меняется соответственно.
...
  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/);
    });
  });

Запустим тест npm run protractor

Также нужно провести рефакторинг одного из модульных тестов из-за добавления свойства mainImageUrl в контроллер PhoneDetailCtrl. Ниже мы создали функцию xyzPhoneData которая возвращает соответствующий JSON с атрибутом images упорядоченным так чтобы проходило тестирование.

...
  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());
    });
  });