Files
angular.js/docs/content/tutorial/step_12.ngdoc
T
Georgios Kalpakas b5dbc9834a docs(tutorial): update to use v1.5.x and best practices
This is a major re-structuring of the tutorial app's codebase, aiming at applying established best
practices (in terms of file naming/layout and code organization) and utilizing several new features
and enhancements (most notably components) introduced in recent versions of Angular (especially
v1.5).

Apart from the overall changes, two new chapters were introduced: one on components and one on code
organization.

--
In the process, several other things were (incidentally) taken care of, including:

* Dependencies were upgraded to latest versions.
* Animations were polished.
* Outdated links were updated.
* The app's base URL was changed to `/` (instead of `/app/`).

BTW, this has been tested with the following versions of Node (on Windows 10) and everything worked
fine:

* 0.11.16
* 4.2.6
* 4.4.2
* 5.10.0
* 6.2.0

--
This was inspired by (and loosely based on) #13834.
Again, mad props to @teropa for leading the way :)

--
**Note:**
The old version of the tutorial, that is compatible with Angular version 1.4 or older, has been
saved on the `pre-v1.5.0-snapshot` branch of
[angular-phonecat](https://github.com/angular/angular-phonecat). The `v1.4.x` version of the
tutorial should be pointed to that branch instead of `master`.

--
Related to angular/angular-phonecat#326.
Related to angular/angular-seed#341.

Closes #14416
2016-05-24 15:44:19 +03:00

186 lines
4.8 KiB
Plaintext

@ngdoc tutorial
@name 12 - Event Handlers
@step 12
@description
<ul doc-tutorial-nav="12"></ul>
In this step, you will add a clickable phone image swapper to the phone details page.
* 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.
<div doc-tutorial-reset="12"></div>
## Component Controller
<br />
**`app/phone-detail/phone-detail.component.js`:**
```js
...
controller: ['$http', '$routeParams',
function PhoneDetailController($http, $routeParams) {
var self = this;
self.setImage = function setImage(imageUrl) {
self.mainImageUrl = imageUrl;
};
$http.get('phones/' + $routeParams.phoneId + '.json').then(function(response) {
self.phone = response.data;
self.setImage(self.phone.images[0]);
});
}
]
...
```
In the `phoneDetail` component's controller, we created the `mainImageUrl` model property and set
its default value to the first phone image URL.
We also created a `setImage()` method (to be used as event handler), that will change the value of
`mainImageUrl`.
## Component Template
<br />
**`app/phone-detail/phone-detail.template.html`:**
```html
<img ng-src="{{$ctrl.mainImageUrl}}" class="phone" />
...
<ul class="phone-thumbs">
<li ng-repeat="img in $ctrl.phone.images">
<img ng-src="{{img}}" ng-click="$ctrl.setImage(img)" />
</li>
</ul>
...
```
We bound the `ngSrc` directive of the large image to the `$ctrl.mainImageUrl` property.
We also registered an {@link ng.directive:ngClick ngClick} handler with thumbnail images. When a
user clicks on one of the thumbnail images, the handler will use the `$ctrl.setImage()` method
callback to change the value of the `$ctrl.mainImageUrl` property to the URL of the clicked
thumbnail image.
<img class="diagram" src="img/tutorial/tutorial_12.png">
# Testing
To verify this new feature, we added two E2E tests. One verifies that `mainImageUrl` is set to the
first phone image URL by default. The second test clicks on several thumbnail images and verifies
that the main image URL changes accordingly.
<br />
**`e2e-tests/scenarios.js`:**
```js
...
describe('View: Phone detail', function() {
...
it('should display the first phone image as the main phone image', function() {
var mainImage = element(by.css('img.phone'));
expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
});
it('should swap the main image when clicking on a thumbnail image', function() {
var mainImage = element(by.css('img.phone'));
var thumbnails = element.all(by.css('.phone-thumbs img'));
thumbnails.get(2).click();
expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/);
thumbnails.get(0).click();
expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
});
});
...
```
You can now rerun the tests with `npm run protractor`.
We also have to refactor one of our unit tests, because of the addition of the `mainImageUrl` model
property to the controller. As previously, we will use a mocked response.
<br />
**`app/phone-detail/phone-detail.component.spec.js`:**
```js
...
describe('controller', function() {
var $httpBackend, ctrl
var xyzPhoneData = {
name: 'phone xyz',
images: ['image/url1.png', 'image/url2.png']
};
beforeEach(inject(function($componentController, _$httpBackend_, _$routeParams_) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData);
...
}));
it('should fetch phone details', function() {
expect(ctrl.phone).toBeUndefined();
$httpBackend.flush();
expect(ctrl.phone).toEqual(xyzPhoneData);
});
});
...
```
Our unit tests should now be passing again.
# Experiments
<div></div>
* Similar to the `ngClick` directive, which binds an Angular expression to the `click` event, there
are built-in directives for all native events, such as `dblclick`, `focus`/`blur`, mouse and key
events, etc.
Let's add a new controller method to the `phoneDetail` component's controller:
```js
self.onDblclick = function onDblclick(imageUrl) {
alert('You double-clicked image: ' + imageUrl);
};
```
and add the following to the `<img>` element in `phone-detail.template.html`:
```html
<img ... ng-dblclick="$ctrl.onDblclick(img)" />
```
Now, whenever you double-click on a thumbnail, an alert pops-up. Pretty annoying!
# Summary
With the phone image swapper in place, we are ready for {@link step_13 step 13} to learn an even
better way to fetch data.
<ul doc-tutorial-nav="12"></ul>