docs(tutorial): synchronize with angular-phonecat changes

This commit is contained in:
Peter Bacon Darwin
2014-04-04 14:05:24 +01:00
parent 6b7a1b82bc
commit 28453015fc
12 changed files with 359 additions and 286 deletions

View File

@@ -12,15 +12,12 @@ dynamically display the same result with any set of data.
In this step you will add some basic information about two cell phones to an HTML page.
- The page now contains a list with information about two phones.
<div doc-tutorial-reset="1"></div>
The page now contains a list with information about two phones.
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-0...step-1):
__`app/index.html`:__
**`app/index.html`:**
```html
<ul>

View File

@@ -9,20 +9,16 @@
Now it's time to make the web page dynamic — with AngularJS. We'll also add a test that verifies the
code for the controller we are going to add.
There are many ways to structure the code for an application. For Angular apps, we encourage the
use of [the Model-View-Controller (MVC)
design pattern](http://en.wikipedia.org/wiki/ModelViewController) to decouple the code and to separate concerns. With that in mind, let's use a
little Angular and JavaScript to add model, view, and controller components to our app.
There are many ways to structure the code for an application. For Angular apps, we encourage the use of
[the Model-View-Controller (MVC) design pattern](http://en.wikipedia.org/wiki/ModelViewController)
to decouple the code and to separate concerns. With that in mind, let's use a little Angular and
JavaScript to add model, view, and controller components to our app.
- The list of three phones is now generated dynamically from data
<div doc-tutorial-reset="2"></div>
The app now contains a list with three phones.
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-1...step-2):
## View and Template
In Angular, the __view__ is a projection of the model through the HTML __template__. This means that
@@ -37,7 +33,7 @@ __`app/index.html`:__
<html ng-app="phonecatApp">
<head>
...
<script src="lib/angular/angular.js"></script>
<script src="../bower_components/angular/angular.js"></script>
<script src="js/controllers.js"></script>
</head>
<body ng-controller="PhoneListCtrl">
@@ -53,21 +49,21 @@ __`app/index.html`:__
</html>
```
We replaced the hard-coded phone list with the
{@link ng.directive:ngRepeat ngRepeat directive} and two
{@link guide/expression Angular expressions} enclosed in curly braces:
`{{phone.name}}` and `{{phone.snippet}}`:
We replaced the hard-coded phone list with the {@link ng.directive:ngRepeat ngRepeat directive}
and two {@link guide/expression Angular expressions}:
* The `ng-repeat="phone in phones"` statement in the `<li>` tag is an Angular repeater. The
repeater tells Angular to create a `<li>` element for each phone in the list using the first `<li>`
* The `ng-repeat="phone in phones"` attribute in the `<li>` tag is an Angular repeater directive.
The repeater tells Angular to create a `<li>` element for each phone in the list using the `<li>`
tag as the template.
* The expressions wrapped in curly braces (`{{phone.name}}` and `{{phone.snippet}}`) will be replaced
by the value of the expressions.
We have added a new directive, called `ng-controller`, which attaches a `PhoneListCtrl`
__controller__ to the DOM at this point.
__controller__ to the DOM at this point:
* As we've learned in {@link step_00 step 0}, the curly braces around `phone.name` and `phone.snippet` denote
bindings. As opposed to evaluating constants, these expressions are referring to our application
model, which was set up in our `PhoneListCtrl` controller.
* The expressions in curly braces (`{{phone.name}}` and `{{phone.snippet}}` denote
bindings, which are referring to our application model, which is set up in our `PhoneListCtrl`
controller.
<img class="diagram" src="img/tutorial/tutorial_02.png">
@@ -128,23 +124,19 @@ To learn more about Angular scopes, see the {@link ng.$rootScope.Scope angular s
## Tests
The "Angular way" of separating controller from the view, makes it easy to test code as it is being
developed. If our controller is available on the global namespace then we can simply instantiate it
with a mock `scope` object. Take a look at the following unit test for our controller:
__`test/unit/controllersSpec.js`:__
developed. If our controller is available on the global namespace then we could simply instantiate it
with a mock `scope` object:
```js
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
describe('PhoneListCtrl', function(){
it('should create "phones" model with 3 phones', function() {
var scope = {},
ctrl = new PhoneListCtrl(scope);
it('should create "phones" model with 3 phones', function() {
var scope = {},
ctrl = new PhoneListCtrl(scope);
expect(scope.phones.length).toBe(3);
});
expect(scope.phones.length).toBe(3);
});
});
```
@@ -154,67 +146,72 @@ Angular. Since testing is such a critical part of software development, we make
tests in Angular so that developers are encouraged to write them.
### Testing non-Global Controllers
In practice, you will not want to have your controller functions in the global namespace. Instead,
we have registered our controllers in the `phonecatApp` module. In this case Angular provides a
service, `$controller`, which will retrieve your controller by name. Here is the same test using
`$controller`:
In practice, you will not want to have your controller functions in the global namespace. Instead,
you can see that we have registered it via an anonymous constructor function on the `phoneCatApp`
module.
In this case Angular provides a service, `$controller`, which will retrieve your controller by name.
Here is the same test using `$controller`:
__`test/unit/controllersSpec.js`:__
```js
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
beforeEach(module('phonecatApp'));
describe('PhoneListCtrl', function(){
it('should create "phones" model with 3 phones', inject(function($controller) {
var scope = {},
ctrl = $controller('PhoneListCtrl', {$scope:scope});
it('should create "phones" model with 3 phones', inject(function($controller) {
var scope = {},
ctrl = $controller('PhoneListCtrl', { $scope: scope });
expect(scope.phones.length).toBe(3);
}));
expect(scope.phones.length).toBe(3);
}));
});
});
```
Don't forget that we need to load up the `phonecatApp` module into the test so that the controller
is available to be injected.
* Before each test we tell Angular to load the `phonecatApp` module.
* We ask Angular to `inject` the `$controller` service into our test function
* We use `$controller` to create an instance of the `PhoneListCtrl`
* With this instance, we verify that the phones array property on the scope contains three records.
### Writing and Running Tests
Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework when
writing tests. Although Angular does not require you to use Jasmine, we wrote all of the tests in
this tutorial in Jasmine. You can learn about Jasmine on the [Jasmine home page](http://jasmine.github.io/) and at the [Jasmine docs](http://jasmine.github.io/).
this tutorial in Jasmine v1.3. You can learn about Jasmine on the [Jasmine home page][jasmine] and
at the [Jasmine docs][jasmine-docs].
The angular-seed project is pre-configured to run all unit tests using [Karma](http://karma-runner.github.io/). Ensure that the necessary karma plugins are installed.
You can do this by issuing `npm install` into your terminal.
The angular-seed project is pre-configured to run unit tests using [Karma][karma] but you will need
to ensure that Karma and its necessary plugins are installed. You can do this by running
`npm install`.
To run the tests, and then watch the files for changes: `npm test`.
To run the test, do the following:
1. In a _separate_ terminal window or tab, go to the `angular-phonecat` directory and run
`npm test` to start the Karma server (the config file necessary to start the server is
located at `./test/karma.conf.js`).
2. Karma will start a new instance of Chrome browser automatically. Just ignore it and let it run in
* Karma will start a new instance of Chrome browser automatically. Just ignore it and let it run in
the background. Karma will use this browser for test execution.
* You should see the following or similar output in the terminal:
3. You should see the following or similar output in the terminal:
info: Karma server started at http://localhost:9876/
info (launcher): Starting browser "Chrome"
info (Chrome 22.0): Connected on socket id tPUm9DXcLHtZTKbAEO-n
Chrome 22.0: Executed 1 of 1 SUCCESS (0.093 secs / 0.004 secs)
<pre>
info: Karma server started at http://localhost:9876/
info (launcher): Starting browser "Chrome"
info (Chrome 22.0): Connected on socket id tPUm9DXcLHtZTKbAEO-n
Chrome 22.0: Executed 1 of 1 SUCCESS (0.093 secs / 0.004 secs)
</pre>
Yay! The test passed! Or not...
4. To rerun the tests, just change any of the source or test .js files. Karma will notice the change
* To rerun the tests, just change any of the source or test .js files. Karma will notice the change
and will rerun the tests for you. Now isn't that sweet?
# Experiments
* Add another binding to `index.html`. For example:
<p>Total number of phones: {{phones.length}}</p>
```html
<p>Total number of phones: {{phones.length}}</p>
```
* Create a new model property in the controller and bind to it from the template. For example:
@@ -255,3 +252,7 @@ to the app.
<ul doc-tutorial-nav="2"></ul>
[jasmine]: http://jasmine.github.io/
[jasmine-docs]: http://jasmine.github.io/1.3/introduction.html
[karma]: http://karma-runner.github.io/

View File

@@ -11,15 +11,10 @@ simple; we will add full text search (yes, it will be simple!). We will also wri
test, because a good end-to-end test is a good friend. It stays with your app, keeps an eye on it,
and quickly detects regressions.
<div doc-tutorial-reset="3"></div>
The app now has a search box. Notice that the phone list on the page changes depending on what a
* The app now has a search box. Notice that the phone list on the page changes depending on what a
user types into the search box.
The most important differences between Steps 2 and 3 are listed below. You can see the full diff on
[GitHub](https://github.com/angular/angular-phonecat/compare/step-2...step-3):
<div doc-tutorial-reset="3"></div>
## Controller
@@ -118,22 +113,26 @@ describe('PhoneCat App', function() {
});
```
This test verifies that the search box and the repeater are correctly wired together. Notice how
easy it is to write end-to-end tests in Angular. Although this example is for a simple test, it
really is that easy to set up any functional, readable, end-to-end test.
### Running End to End Tests with Protractor
Even though the syntax of this test looks very much like our controller unit test written with
Jasmine, the end-to-end test uses APIs of {@link guide/e2e-testing Angular's end-to-end
test runner}.
Jasmine, the end-to-end test uses APIs of [Protractor](https://github.com/angular/protractor). Read
about the Protractor APIs at https://github.com/angular/protractor/blob/master/docs/api.md.
Much like Karma is the test runner for unit tests, we use Protractor to run end-to-end tests.
Try it with `npm run protractor`. End-to-end tests are slow, so unlike with unit tests, Protractor
will exit after the test run and will not automatically rerun the test suite on every file change.
To rerun the test suite, execute `npm run protractor` again.
Note: You must ensure you've installed the protractor and updated webdriver prior to running the
`npm run protractor`. You can do this by issuing `npm install` and `npm run update-webdriver` into
your terminal.
<div class="alert alert-info">
Note: You must ensure you've installed the protractor and updated webdriver prior to running the
`npm run protractor`. You can do this by issuing `npm install` and `npm run update-webdriver` into
your terminal.
</div>
This test verifies that the search box and the repeater are correctly wired together. Notice how
easy it is to write end-to-end tests in Angular. Although this example is for a simple test, it
really is that easy to set up any functional, readable, end-to-end test.
# Experiments

View File

@@ -10,17 +10,13 @@ In this step, you will add a feature to let your users control the order of the
list. The dynamic ordering is implemented by creating a new model property, wiring it together with
the repeater, and letting the data binding magic do the rest of the work.
* In addition to the search box, the app displays a drop down menu that allows users to control the
order in which the phones are listed.
<div doc-tutorial-reset="4"></div>
You should see that in addition to the search box, the app displays a drop down menu that allows
users to control the order in which the phones are listed.
The most important differences between Steps 3 and 4 are listed below. You can see the full diff on
[GitHub](https://github.com/angular/angular-phonecat/compare/step-3...step-4):
## Template
__`app/index.html`:__
@@ -143,7 +139,7 @@ shared by all tests in the parent `describe` block.
You should now see the following output in the Karma tab:
Chrome 22.0: Executed 2 of 2 SUCCESS (0.021 secs / 0.001 secs)
<pre>Chrome 22.0: Executed 2 of 2 SUCCESS (0.021 secs / 0.001 secs)</pre>
Let's turn our attention to the end-to-end test.

View File

@@ -11,14 +11,11 @@ from our server using one of Angular's built-in {@link guide/services services}
ng.$http $http}. We will use Angular's {@link guide/di dependency
injection (DI)} to provide the service to the `PhoneListCtrl` controller.
* There are now a list of 20 phones, loaded from the server.
<div doc-tutorial-reset="5"></div>
You should now see a list of 20 phones.
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-4...step-5):
## Data
The `app/phones/phones.json` file in your project is a dataset that contains a larger list of phones
stored in the JSON format.
@@ -165,9 +162,9 @@ __`test/unit/controllersSpec.js`:__
Because we started using dependency injection and our controller has dependencies, constructing the
controller in our tests is a bit more complicated. We could use the `new` operator and provide the
constructor with some kind of fake `$http` implementation. However, the recommended (and easier) way
is to create a controller in the test environment in the same way that Angular does it in the
production code behind the scenes, as follows:
constructor with some kind of fake `$http` implementation. However, Angular provides a mock `$http`
service that we can use in unit tests. We configure "fake" responses to server requests by calling
methods on a service called $httpBackend:
```js
describe('PhoneCat controllers', function() {
@@ -251,7 +248,7 @@ Finally, we verify that the default value of `orderProp` is set correctly:
You should now see the following output in the Karma tab:
Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)
<pre>Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)</pre>

View File

@@ -10,15 +10,10 @@ In this step, you will add thumbnail images for the phones in the phone list, an
now, will go nowhere. In subsequent steps you will use the links to display additional information
about the phones in the catalog.
* There are now links and images of the phones in the list.
<div doc-tutorial-reset="6"></div>
You should now see links and images of the phones in the list.
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-5...step-6):
## Data
Note that the `phones.json` file contains unique ids and image urls for each of the phones. The

View File

@@ -7,18 +7,49 @@
In this step, you will learn how to create a layout template and how to build an app that has
multiple views by adding routing.
multiple views by adding routing, using an Angular module called 'ngRoute'.
* When you now navigate to `app/index.html`, you are redirected to `app/index.html#/phones`
and the phone list appears in the browser.
* When you click on a phone link the stub of a phone detail page is displayed.
<div doc-tutorial-reset="7"></div>
## Dependencies
Note that when you now navigate to `app/index.html`, you are redirected to `app/index.html#/phones`
and the same phone list appears in the browser. When you click on a phone link the stub of a phone
detail page is displayed.
The routing functionality added by this step is provided by angular in the `ngRoute` module, which
is distributed separately from the core Angular framework.
We are using [Bower][bower] to install client side dependencies. This step updates the
`bower.json` configuration file to include the new dependency:
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-6...step-7).
```json
{
"name": "angular-seed",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-seed",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.2.x",
"angular-mocks": "~1.2.15",
"bootstrap": "~3.1.1",
"angular-route": "~1.2.15"
}
}
```
The new dependency `"angular-route": "~1.2.15"` tells bower to install a version of the
angular-route component that is compatible with version 1.2.15. We must tell bower to download
and install this dependency.
If you have bower installed globally then you can run `bower install` but for this project we have
preconfigured npm to run bower install for us:
```
npm install
```
## Multiple Views, Routing and Layout Template
@@ -34,12 +65,11 @@ template into what we call a "layout template". This is a template that is commo
our application. Other "partial templates" are then included into this layout template depending on
the current "route" — the view that is currently displayed to the user.
Application routes in Angular are declared via the
{@link ngRoute.$routeProvider $routeProvider}, which is the provider of the
{@link ngRoute.$route $route service}. This service makes it easy to wire together
controllers, view templates, and the current
URL location in the browser. Using this feature we can implement [deep linking](http://en.wikipedia.org/wiki/Deep_linking), which lets us utilize the browser's
history (back and forward navigation) and bookmarks.
Application routes in Angular are declared via the {@link ngRoute.$routeProvider $routeProvider},
which is the provider of the {@link ngRoute.$route $route service}. This service makes it easy to
wire together controllers, view templates, and the current URL location in the browser. Using this
feature we can implement [deep linking](http://en.wikipedia.org/wiki/Deep_linking), which lets us
utilize the browser's history (back and forward navigation) and bookmarks.
### A Note About DI, Injector and Providers
@@ -68,108 +98,6 @@ of configuring the injector. As opposed to AMD or require.js modules, Angular mo
solve the problem of script load ordering or lazy script fetching. These goals are totally independent and
both module systems can live side by side and fulfil their goals.
## The App Module
__`app/js/app.js`:__
```js
var phonecatApp = angular.module('phonecatApp', [
'ngRoute',
'phonecatControllers'
]);
phonecatApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: 'PhoneListCtrl'
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: 'PhoneDetailCtrl'
}).
otherwise({
redirectTo: '/phones'
});
}]);
```
In order to configure our application with routes, we need to create a module for our application.
We call this module `phonecatApp`. Notice the second argument passed to `angular.module`:
`['ngRoute', 'phonecatControllers']`. This array lists the modules that `phonecatApp` depends on.
Above, we added `angular-route.js` to `index.html`. That's not all we need to do to be able to use
it, however. We also have to add `ngRoute` as a dependency of our app. To improve the organization
of the app, we're going to move the controllers into their own file (as shown below), and call the
module `phonecatControllers`. By listing these two modules as dependencies of `phonecatApp`, we
can use the directives and services they provide.
Thus using the `config` API we request the `$routeProvider` to be injected into our config function
and use the {@link ngRoute.$routeProvider#when `$routeProvider.when`} API to define our routes.
Our application routes are defined as follows:
* The phone list view will be shown when the URL hash fragment is `/phones`. To construct this
view, Angular will use the `phone-list.html` template and the `PhoneListCtrl` controller.
* The phone details view will be shown when the URL hash fragment matches '/phone/:phoneId', where
`:phoneId` is a variable part of the URL. To construct the phone details view, Angular will use the
`phone-detail.html` template and the `PhoneDetailCtrl` controller.
We reused the `PhoneListCtrl` controller that we constructed in previous steps and we added a new,
empty `PhoneDetailCtrl` controller to the `app/js/controllers.js` file for the phone details view.
`$routeProvider.otherwise({redirectTo: '/phones'})` triggers a redirection to `/phones` when the browser
address doesn't match either of our routes.
Note the use of the `:phoneId` parameter in the second route declaration. The `$route` service uses
the route declaration — `'/phones/:phoneId'` — as a template that is matched against the current
URL. All variables defined with the `:` notation are extracted into the
{@link ngRoute.$routeParams `$routeParams`} object.
In order for our application to bootstrap with our newly created module we'll also need to specify
the module name as the value of the {@link ng.directive:ngApp ngApp}
directive:
__`app/index.html`:__
```html
<!doctype html>
<html lang="en" ng-app="phonecatApp">
...
```
## Controllers
__`app/js/controllers.js`:__
```js
var phonecatControllers = angular.module('phonecatControllers', []);
phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',
function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}]);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',
function($scope, $routeParams) {
$scope.phoneId = $routeParams.phoneId;
}]);
```
Again, note that we created a new module called `phonecatControllers`. For small AngularJS applications,
it's common to create just one module for all of your controllers if there are just a few. For larger apps,
you will probably want to create separate modules for each major feature of your app.
Because our example app is relatively small, we'll add all of our controllers to this module.
## Template
The `$route` service is usually used in conjunction with the {@link ngRoute.directive:ngView
@@ -177,9 +105,8 @@ ngView} directive. The role of the `ngView` directive is to include the view tem
route into the layout template. This makes it a perfect fit for our `index.html` template.
<div class="alert alert-info">
**Note:** Starting with AngularJS version 1.2, `ngRoute` is in its own module and must be loaded by loading
the `angular-route.js` file distributed with Angular. The easiest way to load the file is to add a `<script>`
tag to your `index.html` file as shown below.
**Note:** Starting with AngularJS version 1.2, `ngRoute` is in its own module and must be loaded by
loading the additional `angular-route.js` file, which we download via Bower above.
</div>
__`app/index.html`:__
@@ -189,8 +116,8 @@ __`app/index.html`:__
<html lang="en" ng-app="phonecatApp">
<head>
...
<script src="lib/angular/angular.js"></script>
<script src="lib/angular/angular-route.js"></script>
<script src="../bower_components/angular/angular.js"></script>
<script src="../bower_components/angular-route/angular-route.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
</head>
@@ -202,6 +129,12 @@ __`app/index.html`:__
</html>
```
We have added to extra `<script>` tags in our index file to load up extra JavaScript files into our
application:
- `angular-route.js` : defines the `ngRoute` module.
- `controllers.js` : defines a new `phonecatControllers` module.
Note that we removed most of the code in the `index.html` template and replaced it with a single
line containing a div with the `ng-view` attribute. The code that we removed was placed into the
`phone-list.html` template:
@@ -251,7 +184,95 @@ __`app/partials/phone-detail.html`:__
TBD: detail view for {{phoneId}}
```
Note how we are using `phoneId` model defined in the `PhoneDetailCtrl` controller.
Note how we are using the `phoneId` expression which will be defined in the `PhoneDetailCtrl` controller.
## The App Module
__`app/js/app.js`:__
```js
var phonecatApp = angular.module('phonecatApp', [
'ngRoute',
'phonecatControllers'
]);
phonecatApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: 'PhoneListCtrl'
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: 'PhoneDetailCtrl'
}).
otherwise({
redirectTo: '/phones'
});
}]);
```
Above, we added `angular-route.js` and `controllers.js` to `index.html`. That's not all we need to
do to be able to use their code, however. We also have to add the modules dependencies of our app.
To improve the organization of the app, we've moved the controllers into their own file defining its
own module `phonecatControllers` (as shown below). By listing these two modules as dependencies of
`phonecatApp`, we can use the directives and services they provide. Notice the second argument
passed to `angular.module`, `['ngRoute', 'phonecatControllers']`. This array lists the modules that
`phonecatApp` depends on.
Using the `config` API we request the `$routeProvider` to be injected into our config function
and use the {@link ngRoute.$routeProvider#when `$routeProvider.when`} API to define our routes.
Our application routes are defined as follows:
* The phone list view will be shown when the URL hash fragment is `/phones`. To construct this
view, Angular will use the `phone-list.html` template and the `PhoneListCtrl` controller.
* The phone details view will be shown when the URL hash fragment matches '/phone/:phoneId', where
`:phoneId` is a variable part of the URL. To construct the phone details view, Angular will use the
`phone-detail.html` template and the `PhoneDetailCtrl` controller.
We reused the `PhoneListCtrl` controller that we constructed in previous steps and we added a new,
empty `PhoneDetailCtrl` controller to the `app/js/controllers.js` file for the phone details view.
`$routeProvider.otherwise({redirectTo: '/phones'})` triggers a redirection to `/phones` when the browser
address doesn't match either of our routes.
Note the use of the `:phoneId` parameter in the second route declaration. The `$route` service uses
the route declaration — `'/phones/:phoneId'` — as a template that is matched against the current
URL. All variables defined with the `:` notation are extracted into the
{@link ngRoute.$routeParams `$routeParams`} object.
## Controllers
__`app/js/controllers.js`:__
```js
var phonecatControllers = angular.module('phonecatControllers', []);
phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',
function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}]);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',
function($scope, $routeParams) {
$scope.phoneId = $routeParams.phoneId;
}]);
```
Again, note that we created a new module called `phonecatControllers`. For small AngularJS applications,
it's common to create just one module for all of your controllers if there are just a few. For larger apps,
you will probably want to create separate modules for each major feature of your app.
Because our example app is relatively small, we'll add all of our controllers to this module.
## Test
@@ -307,3 +328,6 @@ step 8} to implement the phone details view.
<ul doc-tutorial-nav="7"></ul>
[bower]: http://bower.io

View File

@@ -9,18 +9,15 @@
In this step, you 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 used {@link ng.$http $http} to fetch our data, and we
fleshed out the `phone-detail.html` view template.
<div doc-tutorial-reset="8"></div>
Now 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 will use {@link ng.$http $http} to fetch
our data, and we'll flesh out the `phone-detail.html` view template.
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-7...step-8):
## Data
In addition to `phones.json`, the `app/phones/` directory also contains one json file for each
@@ -153,7 +150,7 @@ __`test/unit/controllersSpec.js`:__
You should now see the following output in the Karma tab:
Chrome 22.0: Executed 3 of 3 SUCCESS (0.039 secs / 0.012 secs)
<pre>Chrome 22.0: Executed 3 of 3 SUCCESS (0.039 secs / 0.012 secs)</pre>
We also added a new end-to-end test that navigates to the Nexus S detail page and verifies that the

View File

@@ -5,20 +5,13 @@
<ul doc-tutorial-nav="9"></ul>
In this step you will learn how to create your own custom display filter.
<div doc-tutorial-reset="9"></div>
Navigate to one of the detail pages.
In the previous step, the details page displayed either "true" or "false" to indicate whether
* In the previous step, the details page displayed either "true" or "false" to indicate whether
certain phone features were present or not. We have used a custom filter to convert those text
strings into glyphs: ✓ for "true", and ✘ for "false". Let's see what the filter code looks like.
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-8...step-9):
<div doc-tutorial-reset="9"></div>
## Custom Filter
@@ -118,7 +111,7 @@ access to the filter that we want to test. See {@link angular.mock.inject angul
You should now see the following output in the Karma tab:
Chrome 22.0: Executed 4 of 4 SUCCESS (0.034 secs / 0.012 secs)
<pre>Chrome 22.0: Executed 4 of 4 SUCCESS (0.034 secs / 0.012 secs)</pre>
# Experiments
@@ -131,9 +124,11 @@ following bindings to `index.html`:
* `{{ 1304375948024 | date:"MM/dd/yyyy @ h:mma" }}`
* We can also create a model with an input element, and combine it with a filtered binding. Add
the following to index.html:
the following to index.html:
<input ng-model="userInput"> Uppercased: {{ userInput | uppercase }}
```html
<input ng-model="userInput"> Uppercased: {{ userInput | uppercase }}
```
# Summary

View File

@@ -8,16 +8,11 @@
In this step, you will add a clickable phone image swapper to the phone details page.
<div doc-tutorial-reset="10"></div>
The phone details view displays one large image of the current phone and several smaller thumbnail
* 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.
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-9...step-10):
<div doc-tutorial-reset="10"></div>
## Controller

View File

@@ -6,37 +6,72 @@
<ul doc-tutorial-nav="11"></ul>
In this step, you will improve the way our app fetches data.
In this step, you will change the way our app fetches data.
* We defined a custom service that represents a [RESTful][restful] client. Using this client we
can make requests to the server for data in an easier way, without having to deal with the
lower-level {@link ng.$http $http} API, HTTP methods and URLs.
<div doc-tutorial-reset="11"></div>
## Dependencies
The next improvement we will make to our app is to define a custom service that represents a [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) client. Using this client we
can make XHR requests for data in an easier way, without having to deal with the lower-level {@link
ng.$http $http} API, HTTP methods and URLs.
The RESTful functionality is provided by Angular in the `ngResource` module, which is distributed
separately from the core Angular framework.
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-10...step-11):
We are using [Bower][bower] to install client side dependencies. This step updates the
`bower.json` configuration file to include the new dependency:
```
{
"name": "angular-seed",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-seed",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.2.x",
"angular-mocks": "~1.2.15",
"bootstrap": "~3.1.1",
"angular-route": "~1.2.15",
"angular-resource": "~1.2.15"
}
}
```
The new dependency `"angular-resource": "~1.2.15"` tells bower to install a version of the
angular-resource component that is compatible with version 1.2.15. We must tell bower to download
and install this dependency.
If you have bower installed globally then you can run `bower install` but for this project we have
preconfigured npm to run bower install for us:
```
npm install
```
## Template
The custom service is defined in `app/js/services.js` so we need to include this file in our layout
template. Additionally, we also need to load the `angular-resource.js` file, which contains the
{@link api/ngResource ngResource} module and in it the {@link api/ngResource.$resource $resource}
service, that we'll soon use:
Our custom resource service will be defined in `app/js/services.js` so we need to include this file
in our layout template. Additionally, we also need to load the `angular-resource.js` file, which
contains the {@link api/ngResource ngResource} module:
__`app/index.html`.__
```html
...
<script src="../bower_components/angular-resource/angular-resource.js"></script>
<script src="js/services.js"></script>
<script src="lib/angular/angular-resource.js"></script>
...
```
## Service
We create our own service to provide access to the phone data on the server:
__`app/js/services.js`.__
```js
@@ -52,13 +87,12 @@ phonecatServices.factory('Phone', ['$resource',
We used the module API to register a custom service using a factory function. We passed in the name
of the service - 'Phone' - and the factory function. The factory function is similar to a
controller's constructor in that both can declare dependencies via function arguments. The Phone
service declared a dependency on the `$resource` service.
controller's constructor in that both can declare dependencies to be injected via function
arguments. The Phone service declared a dependency on the `$resource` service.
The {@link ngResource.$resource `$resource`} service makes it easy to create a
[RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) client with just a few
lines of code. This client can then be used in our application, instead of the lower-level {@link
ng.$http $http} service.
[RESTful][restful] client with just a few lines of code. This client can then be used in our
application, instead of the lower-level {@link ng.$http $http} service.
__`app/js/app.js`.__
@@ -135,8 +169,8 @@ service correctly.
The {@link ngResource.$resource $resource} service augments the response object
with methods for updating and deleting the resource. If we were to use the standard `toEqual`
matcher, our tests would fail because the test values would not match the responses exactly. To
solve the problem, we use a newly-defined `toEqualData` [Jasmine matcher](https://github.com/pivotal/jasmine/wiki/Matchers). When the
`toEqualData` matcher compares two objects, it takes only object properties into account and
solve the problem, we use a newly-defined `toEqualData` [Jasmine matcher][jasmine-matchers]. When
the `toEqualData` matcher compares two objects, it takes only object properties into account and
ignores methods.
@@ -217,7 +251,7 @@ describe('PhoneCat controllers', function() {
You should now see the following output in the Karma tab:
Chrome 22.0: Executed 4 of 4 SUCCESS (0.038 secs / 0.01 secs)
<pre>Chrome 22.0: Executed 4 of 4 SUCCESS (0.038 secs / 0.01 secs)</pre>
# Summary
@@ -227,3 +261,6 @@ learn how to improve this application with animations.
<ul doc-tutorial-nav="11"></ul>
[restful]: http://en.wikipedia.org/wiki/Representational_State_Transfer
[jasmine-matchers]: https://github.com/pivotal/jasmine/wiki/Matchers

View File

@@ -9,22 +9,57 @@
In this final step, we will enhance our phonecat web application by attaching CSS and JavaScript
animations on top of the template code we created before.
* Used the `ngAnimate` to enable animations throughout the application.
* Common `ng` directives automatically trigger hooks for animations to tap into.
* When an animation is found then the animation will run in between the standard DOM operation that
is being issued on the element at the given time (e.g. inserting and removing nodes on
{@link api/ng.directive:ngRepeat `ngRepeat`} or adding and removing classes on
{@link api/ng.directive:ngClass `ngClass`}).
<div doc-tutorial-reset="12"></div>
## Dependencies
Now that everything is set in place for a fully functional web application, we can attach CSS and JavaScript
animations to common directives that are used to render our application. AngularJS comes bundled with an
additional JavaScript file called `angular-animate.js` which, when included into the website and set as
a dependency with the application module, will enable animations throughout the application.
The animation functionality is provided by Angular in the `ngAnimate` module, which is distributed
separately from the core Angular framework. In addition we will use `JQuery` in this project to do
extra JavaScript animations.
Common `ng` directives automatically trigger hooks for animations to tap into. When an animation is found
then the animation will run in between the standard DOM operation that is being issued on the element at
the given time (e.g. inserting and removing nodes on {@link api/ng.directive:ngRepeat `ngRepeat`} or adding
and removing classes on {@link api/ng.directive:ngClass `ngClass`}).
We are using [Bower][bower] to install client side dependencies. This step updates the
`bower.json` configuration file to include the new dependency:
The most important changes are listed below. You can see the full diff on
[GitHub](https://github.com/angular/angular-phonecat/compare/step-11...step-12):
```
{
"name": "angular-seed",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-seed",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.2.x",
"angular-mocks": "~1.2.15",
"bootstrap": "~3.1.1",
"angular-route": "~1.2.15",
"angular-resource": "~1.2.15",
"jquery": "1.10.2",
"angular-animate": "~1.2.15"
}
}
```
* `"angular-animate": "~1.2.15"` tells bower to install a version of the
angular-animate component that is compatible with version 1.2.15.
* `"jquery": "1.10.2"` tells bower to install the 1.1.2 version of JQuery. Note that this is not an
Angular library, it is the standard JQuery library. We can use bower to install a wide range of 3rd
party libraries.
We must tell bower to download and install these dependencies. If you have bower installed globally
then you can run `bower install` but for this project we have preconfigured npm to run bower install
for us:
```
npm install
```
## How Animations work with `ngAnimate`
@@ -46,17 +81,22 @@ __`app/index.html`.__
```html
...
<!-- for CSS Transitions and/or Keyframe Animations -->
<link rel="stylesheet" href="css/animations.css">
...
<!-- jQuery is used for JavaScript animations (include this before angular.js) -->
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="../bower_components/jquery/jquery.js"></script>
...
<!-- required module to enable animation support in AngularJS -->
<script src="lib/angular/angular-animate.js"></script>
<script src="../bower_components/angular/angular-animate.js"></script>
<!-- for JavaScript Animations -->
<script src="js/animations.js"></script>
<!-- for CSS Transitions and/or Keyframe Animations -->
<link rel="stylesheet" href="css/animations.css">
...
```