EVOLUTION-MANAGER
Edit File: step_10.html
<a href='https://github.com/angular/angular.js/edit/v1.3.x/docs/content/tutorial/step_10.ngdoc?message=docs(tutorial%2F10 - Event Handlers)%3A%20describe%20your%20change...' class='improve-docs btn btn-primary'><i class="glyphicon glyphicon-edit"> </i>Improve this Doc</a> <ul doc-tutorial-nav="10"></ul> <p>In this step, you will add a clickable phone image swapper to the phone details page.</p> <ul> <li>The phone details view displays one large image of the current phone and several smaller thumbnail images. It would be great if we could replace the large image with any of the thumbnails just by clicking on the desired thumbnail image. Let's have a look at how we can do this with Angular.</li> </ul> <div doc-tutorial-reset="10"></div> <h2 id="controller">Controller</h2> <p><strong><code>app/js/controllers.js</code>:</strong></p> <pre><code class="lang-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; } }]); </code></pre> <p>In the <code>PhoneDetailCtrl</code> controller, we created the <code>mainImageUrl</code> model property and set its default value to the first phone image URL.</p> <p>We also created a <code>setImage</code> event handler function that will change the value of <code>mainImageUrl</code>.</p> <h2 id="template">Template</h2> <p><strong><code>app/partials/phone-detail.html</code>:</strong></p> <pre><code class="lang-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> ... </code></pre> <p>We bound the <code>ngSrc</code> directive of the large image to the <code>mainImageUrl</code> property.</p> <p>We also registered an <a href="api/ng/directive/ngClick"><code>ngClick</code></a> handler with thumbnail images. When a user clicks on one of the thumbnail images, the handler will use the <code>setImage</code> event handler function to change the value of the <code>mainImageUrl</code> property to the URL of the thumbnail image.</p> <div style="display: none"> TODO! <img class="diagram" src="img/tutorial/tutorial_10-11_final.png"> </div> <h2 id="test">Test</h2> <p>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.</p> <p><strong><code>test/e2e/scenarios.js</code>:</strong></p> <pre><code class="lang-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/); }); }); </code></pre> <p>You can now rerun <code>npm run protractor</code> to see the tests run.</p> <p>You also have to refactor one of your unit tests because of the addition of the <code>mainImageUrl</code> model property to the <code>PhoneDetailCtrl</code> controller. Below, we create the function <code>xyzPhoneData</code> which returns the appropriate json with the <code>images</code> attribute in order to get the test to pass.</p> <p><strong><code>test/unit/controllersSpec.js</code>:</strong></p> <pre><code class="lang-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()); }); }); </code></pre> <p>Your unit tests should now be passing.</p> <h1 id="experiments">Experiments</h1> <ul> <li><p>Let's add a new controller method to <code>PhoneDetailCtrl</code>:</p> <pre><code>$scope.hello = function(name) { alert('Hello ' + (name || 'world') + '!'); } </code></pre> <p>and add:</p> <pre><code><button ng-click="hello('Elmo')">Hello</button> </code></pre> <p>to the <code>phone-detail.html</code> template.</p> </li> </ul> <div style="display: none"> TODO! The controller methods are inherited between controllers/scopes, so you can use the same snippet in the <code>phone-list.html</code> template as well. * Move the <code>hello</code> method from <code>PhoneCatCtrl</code> to <code>PhoneListCtrl</code> and you'll see that the button declared in <code>index.html</code> will stop working, while the one declared in the <code>phone-list.html</code> template remains operational. </div> <h1 id="summary">Summary</h1> <p>With the phone image swapper in place, we're ready for <a href="tutorial/step_11">step 11</a> to learn an even better way to fetch data.</p> <ul doc-tutorial-nav="10"></ul>