b5dbc9834a
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
215 lines
5.0 KiB
Plaintext
215 lines
5.0 KiB
Plaintext
@ngdoc tutorial
|
|
@name 10 - More Templating
|
|
@step 10
|
|
@description
|
|
|
|
<ul doc-tutorial-nav="10"></ul>
|
|
|
|
|
|
In this step, we will implement the phone details view, which is displayed when a user clicks on a
|
|
phone in the phone list.
|
|
|
|
* When you click on a phone on the list, the phone details page with phone-specific information is
|
|
displayed.
|
|
|
|
To implement the phone details view we are going to use {@link ng.$http $http} to fetch our data,
|
|
and then flesh out the `phoneDetail` component's template.
|
|
|
|
|
|
<div doc-tutorial-reset="10"></div>
|
|
|
|
|
|
## Data
|
|
|
|
In addition to `phones.json`, the `app/phones/` directory also contains one JSON file for each
|
|
phone:
|
|
|
|
<br />
|
|
**`app/phones/nexus-s.json`:** (sample snippet)
|
|
|
|
```json
|
|
{
|
|
"additionalFeatures": "Contour Display, Near Field Communications (NFC), ...",
|
|
"android": {
|
|
"os": "Android 2.3",
|
|
"ui": "Android"
|
|
},
|
|
...
|
|
"images": [
|
|
"img/phones/nexus-s.0.jpg",
|
|
"img/phones/nexus-s.1.jpg",
|
|
"img/phones/nexus-s.2.jpg",
|
|
"img/phones/nexus-s.3.jpg"
|
|
],
|
|
"storage": {
|
|
"flash": "16384MB",
|
|
"ram": "512MB"
|
|
}
|
|
}
|
|
```
|
|
|
|
Each of these files describes various properties of the phone using the same data structure. We will
|
|
show this data in the phone details view.
|
|
|
|
|
|
## Component Controller
|
|
|
|
We will expand the `phoneDetail` component's controller by using the `$http` service to fetch the
|
|
appropriate JSON files. This works the same way as the `phoneList` component's controller.
|
|
|
|
<br />
|
|
**`app/phone-detail/phone-detail.component.js`:**
|
|
|
|
```js
|
|
angular.
|
|
module('phoneDetail').
|
|
component('phoneDetail', {
|
|
templateUrl: 'phone-detail/phone-detail.template.html',
|
|
controller: ['$http', '$routeParams',
|
|
function PhoneDetailController($http, $routeParams) {
|
|
var self = this;
|
|
|
|
$http.get('phones/' + $routeParams.phoneId + '.json').then(function(response) {
|
|
self.phone = response.data;
|
|
});
|
|
}
|
|
]
|
|
});
|
|
```
|
|
|
|
To construct the URL for the HTTP request, we use `$routeParams.phoneId`, which is extracted from
|
|
the current route by the `$route` service.
|
|
|
|
|
|
## Component Template
|
|
|
|
The inline, TBD placeholder template has been replaced with a full blown external template,
|
|
including lists and bindings that comprise the phone details. Note how we use the Angular
|
|
`{{expression}}` markup and `ngRepeat` to project phone data from our model into the view.
|
|
|
|
<br />
|
|
**`app/phone-detail/phone-detail.template.html`:**
|
|
|
|
```html
|
|
<img ng-src="{{$ctrl.phone.images[0]}}" class="phone" />
|
|
|
|
<h1>{{$ctrl.phone.name}}</h1>
|
|
|
|
<p>{{$ctrl.phone.description}}</p>
|
|
|
|
<ul class="phone-thumbs">
|
|
<li ng-repeat="img in $ctrl.phone.images">
|
|
<img ng-src="{{img}}" />
|
|
</li>
|
|
</ul>
|
|
|
|
<ul class="specs">
|
|
<li>
|
|
<span>Availability and Networks</span>
|
|
<dl>
|
|
<dt>Availability</dt>
|
|
<dd ng-repeat="availability in $ctrl.phone.availability">{{availability}}</dd>
|
|
</dl>
|
|
</li>
|
|
...
|
|
<li>
|
|
<span>Additional Features</span>
|
|
<dd>{{$ctrl.phone.additionalFeatures}}</dd>
|
|
</li>
|
|
</ul>
|
|
```
|
|
|
|
<img class="diagram" src="img/tutorial/tutorial_10.png">
|
|
|
|
|
|
# Testing
|
|
|
|
We wrote a new unit test that is similar to the one we wrote for the `phoneList` component's
|
|
controller in {@link step_07#testing step 7}.
|
|
|
|
<br />
|
|
**`app/phone-detail/phone-detail.component.spec.js`:**
|
|
|
|
```js
|
|
describe('phoneDetail', function() {
|
|
|
|
// Load the module that contains the `phoneDetail` component before each test
|
|
beforeEach(module('phoneDetail'));
|
|
|
|
// Test the controller
|
|
describe('PhoneDetailController', function() {
|
|
var $httpBackend, ctrl;
|
|
|
|
beforeEach(inject(function($componentController, _$httpBackend_, $routeParams) {
|
|
$httpBackend = _$httpBackend_;
|
|
$httpBackend.expectGET('phones/xyz.json').respond({name: 'phone xyz'});
|
|
|
|
$routeParams.phoneId = 'xyz';
|
|
|
|
ctrl = $componentController('phoneDetail');
|
|
}));
|
|
|
|
it('should fetch the phone details', function() {
|
|
expect(ctrl.phone).toBeUndefined();
|
|
|
|
$httpBackend.flush();
|
|
expect(ctrl.phone).toEqual({name: 'phone xyz'});
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
```
|
|
|
|
You should now see the following output in the Karma tab:
|
|
|
|
```
|
|
Chrome 49.0: Executed 3 of 3 SUCCESS (0.159 secs / 0.136 secs)
|
|
```
|
|
|
|
We also added a new E2E test that navigates to the 'Nexus S' details page and verifies that the
|
|
heading on the page is "Nexus S".
|
|
|
|
<br />
|
|
**`e2e-tests/scenarios.js`**
|
|
|
|
```js
|
|
...
|
|
|
|
describe('View: Phone detail', function() {
|
|
|
|
beforeEach(function() {
|
|
browser.get('index.html#!/phones/nexus-s');
|
|
});
|
|
|
|
it('should display the `nexus-s` page', function() {
|
|
expect(element(by.binding('$ctrl.phone.name')).getText()).toBe('Nexus S');
|
|
});
|
|
|
|
});
|
|
|
|
...
|
|
```
|
|
|
|
You can run the tests with `npm run protractor`.
|
|
|
|
|
|
# Experiments
|
|
|
|
<div></div>
|
|
|
|
* Using [Protractor's API][protractor-docs], write a test that verifies that we display 4 thumbnail
|
|
images on the 'Nexus S' details page.
|
|
|
|
|
|
# Summary
|
|
|
|
Now that the phone details view is in place, proceed to {@link step_11 step 11} to learn how to
|
|
write your own custom display filter.
|
|
|
|
|
|
<ul doc-tutorial-nav="10"></ul>
|
|
|
|
|
|
[protractor-docs]: https://angular.github.io/protractor/#/api
|