mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-05-09 20:06:24 +08:00
docs(tutorial): synchronize with angular-phonecat changes
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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/Model–View–Controller) 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/Model–View–Controller)
|
||||
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/
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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">
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user