diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/content/tutorial/index.ngdoc | 155 | ||||
| -rwxr-xr-x | docs/content/tutorial/step_00.ngdoc | 167 | ||||
| -rwxr-xr-x | docs/content/tutorial/step_01.ngdoc | 157 | ||||
| -rwxr-xr-x | docs/content/tutorial/step_02.ngdoc | 311 | ||||
| -rwxr-xr-x | docs/content/tutorial/step_03.ngdoc | 250 | ||||
| -rwxr-xr-x | docs/content/tutorial/step_04.ngdoc | 362 | ||||
| -rwxr-xr-x | docs/content/tutorial/step_05.ngdoc | 366 | ||||
| -rwxr-xr-x | docs/content/tutorial/step_06.ngdoc | 232 | ||||
| -rwxr-xr-x | docs/content/tutorial/step_07.ngdoc | 392 | ||||
| -rwxr-xr-x | docs/content/tutorial/step_08.ngdoc | 348 | ||||
| -rwxr-xr-x | docs/content/tutorial/step_09.ngdoc | 235 | ||||
| -rw-r--r-- | docs/content/tutorial/step_10.ngdoc | 87 | ||||
| -rw-r--r-- | docs/content/tutorial/step_11.ngdoc | 426 |
13 files changed, 1900 insertions, 1588 deletions
diff --git a/docs/content/tutorial/index.ngdoc b/docs/content/tutorial/index.ngdoc index b430b248..efe30573 100644 --- a/docs/content/tutorial/index.ngdoc +++ b/docs/content/tutorial/index.ngdoc @@ -1,4 +1,3 @@ -@workInProgress @ngdoc overview @name Tutorial @description @@ -6,63 +5,56 @@ A great way to get introduced to angular is to work through the {@link tutorial.step_00 angular tutorial}, which walks you through the construction of an angular web app. The app you will build in the tutorial is loosely based on the {@link http://www.google.com/phone/ Google phone gallery -app}. The {@link http://angular.github.com/angular-phonecat/step-11/app/ end result of our effort} -is visually simpler, but demonstrates many of the angular features without distractions in the -form of CSS code. +app}. The {@link http://angular.github.com/angular-phonecat/step-11/app/#/phones end result of our +effort} is visually simpler, but demonstrates many of the angular features without distractions in +the form of CSS code. -This tutorial app ends up like a Google phone gallery app, but is originally based on the {@link -https://github.com/angular/angular-seed angular-seed project}. The angular seed app isn't -necessary for building angular apps, but it helps you get started quickly and makes the -development and testing process much easier. Angular-seed includes a simple example, the latest -angular libraries, test libraries, and scripts. It provides all of these in an environment that -is pre-configured for developing a typical web app. +The starting point for our tutorial is the {@link https://github.com/angular/angular-seed +angular-seed project}. -Once you set up your tutorial environment, you should be able to get through the material in less -than a day and you'll have fun doing it. More experienced coders may be able to zip through the -exercises in an afternoon. In any case, we promise that your time will be well spent! +The angular-seed project includes a simple example app, the latest angular libraries, test +libraries, and scripts. It provides all of these in an environment that is pre-configured for +developing a typical web app. For this tutorial, we modified the angular-seed as follows: + +* Removed the example app +* Added phone images to `app/img/phones` +* Added phone data files (JSON) to `app/phones` + + Note: Using the angular seed app isn't required for building angular apps, but doing so helps + you get started quickly and makes the development and testing process much easier. When you finish the tutorial you will be able to: * Create a simple dynamic application that works in any browser * Define the differences between angular and common JavaScript frameworks -* Understand angular expressions * Understand how data binding works in angular * Use the angular-seed project to quickly boot-strap your own projects * Create and run tests * Identify resources for learning more about angular -You can work through the tutorial in any of the following ways: - -* <a href="#UsingGit">Using Git</a>. Use the Git versioning system to get the files for each step. -* <a href="#UsingSnapshots">Using Snapshots</a>. Download snapshots (files for each step of the -tutorial) and tinker with them. -* <a href="#ReadingExamples">Reading the Examples</a>. Read through the examples, and inspect -results and code on our server. +Mac and Linux users can work through the tutorial, run tests, and experiment with the code using +Git or the snapshots described below. Windows users will be able follow the tutorial and read +through the source code and view the application running on our servers at different stages. -The first two ways (Git and snapshots) give you a fuller experience, in that you can run the unit -and end-to-end tests in addition to the tutorial app. They also give you the ability to play -around with the code and get instant feedback in your browser. The last way (reading through the -tutorial online) requires no setup on your machine, but you can't run the tests, and it won't be -as easy to play around with the code. +You can go through the whole tutorial in a couple of hours or you may want to spend a pleasant day +really digging into it. In any case, we promise that your time will be well spent! <a name="PreReqs"></a> -# Prerequisites for Git and Snapshots +# Prerequisites -To run the tutorial app and tests on your machine (using Git or the snapshots) you will need the -following: +To run the tutorial app and tests on your machine you will need the following: -* You need to be running on a Mac or Linux machine. +* A Mac or Linux machine (required by the tutorial scripts, not angular) * An http server running on your system. If you don't already have one installed, you can install `node.js` ({@link https://github.com/joyent/node/wiki/Installation node.js install}) or another http sever (such as Apache, etc.). -* Java. This is required for running tests. Angular itself doesn't require Java. -* A modern browser (including IE8+). Needed for viewing and debugging code. -* A text editor of your choice. +* Java. This is only required for if you want to run tests via JsTestDriver. +* A web browser. +* A text editor. -<a name="UsingGit"></a> # Using Git -The following instructions are for developers who are comfortable with Git's versioning system: +The following instructions are for developers who are comfortable with Git versioning system: 1. Check to be sure you have all of the <a href="#PreReqs">prerequisites</a> on your system. @@ -70,40 +62,19 @@ The following instructions are for developers who are comfortable with Git's ver https://github.com/angular/angular-phonecat angular-phonecat} by running the following command in a terminal: - git clone git://github.com/angular/angular-phonecat.git - - This will create a directory called `angular-phonecat`. - -3. In terminal, navigate to the `angular-phonecat` directory and run: + git clone git://github.com/angular/angular-phonecat.git - git checkout step-0 + This will create a directory called `angular-phonecat` in the current directory. - (You can run `git checkout step-[0-11]` to go to any of the steps in the tutorial). +3. Change your current directory to `angular-phonecat`. -4. To see the app running in a browser, do the following: - * __For node.js users:__ - 1. Run `./scripts/web-server.js` to start the app server. - 2. Open a browser window for the app and navigate to http://localhost:8000/app/index.html. + cd angular-phonecat - * __For other http servers:__ - 1. Configure the server to serve the files in the `angular-phonecat` directory. - 2. Run `./scripts/web-server.js` to start the app server. - 3. Navigate in your browser to - http://localhost:[*port-number*]/[*context-path*]/app/index.html. + The tutorial instructions assume you are running all commands from this directory. -5. To see tests running in a browser, do the following: - * __For node.js users:__ - 1. Run `./scripts/test-server.sh` to start the test web server. - 2. Open a browser window for the tests, navigate to http://localhost:9876, and choose - "strict mode". - * __For other http servers:__ - 1. Configure the server to serve the files in the `angular-phonecat` directory. - 1. Run `./scripts/test-server.sh` to start the test web server. - 3. Navigate in your browser to http://localhost:[*port-number*]/, and choose "strict mode". +Read the Tutorial Navigation section, then navigate to Step 0. - -<a name="UsingSnapshots"></a> # Using Snapshots Snapshots are the sets of files that reflect the state of the tutorial app at each step. These @@ -113,60 +84,18 @@ knowledge of Git. You can download and install the snapshot files as follows: 1. Check to be sure you have all of the <a href="#PreReqs">prerequisites</a> on your system. -2. Navigate to [*the angular server*], and download and unzip [*the snapshot file*] to an -[*install-dir*] of your choosing. +2. Navigate to [*the angular server*], download and then unzip [*the snapshot file*] to an +[*install-dir*]. 3. Change directories to [*install-dir*]/sandbox. -4. Run the following command: - * `./goto_step.sh 0` - - You have to start out at the beginning, which is Step 0. After you set up Step 0, you can skip - around between any steps. - -1. To see the app running in your browser, do the following: - * __For node.js users:__ - 1. Run `./scripts/web-server.js` to run the web server. - 2. Open a browser window for the app and navigate to http://localhost:8000/app/index.html. - 3. Open a browser window for the tests, navigate to http://localhost:9876, and choose - "strict mode". - - * __For other http servers:__ - 1. Configure servers to serve the app and test files in the [*install-dir*]/sandbox. - 2. Start the server. - 3. Navigate in your app browser to - http://localhost:[*port-number*]/[*context-path*]/app/index.html. - 4. Navigate in your test browser to http://localhost:[*port-number*] and choose "strict - mode". - -1. To view the tutorial app at different steps, run `./goto_step.sh [0-11]` and then refresh your -browser. For example, say you're on Step 5 of the tutorial, and you want to see the app in action: - - 1. Run `goto_step.sh 5` from the command line in the `sandbox` directory. - 1. Refresh your app browser. - -<a name="ReadingExamples"></a> -# Reading the Examples - -If you don't want to set up anything on your local machine, you can read through the tutorial and -inspect the tutorial files on our servers; doing this will give you a good idea of what angular -does, but you won't be able to make any code changes and experiment on your own. - -To see the running app at each tutorial step, click the "Example" link at the top or bottom of -each tutorial page. - -To view the code differences between tutorial steps, click the Code Diff link at top or bottom of -each tutorial page. Additions are highlighted in green; deletions are highlighted in red. - - -# Relative URLs -Throughout the tutorial, we use relative URLs to refer to files hosted on our local http server. -The absolute URL depends on your configuration. For example, if you are using the node.js server, -`app/index.html` translates to: + cd [*install-dir*]/sandbox - http://localhost:8000/app/index.html +Read the Tutorial Navigation section, then navigate to step-0. -If you are using your own http server running on port 8080 and the tutorial files are hosted at -`/angular_tutorial`, `app/index.html` translates to: +# Tutorial Navigation - http://localhost:8080/angular_tutorial/app/index.html +To see the app running on the angular server, click the "Live Demo" link at the top or bottom of +any tutorial page. To view the code differences between tutorial steps, click the Code Diff link +at top or bottom of each tutorial page. In the Code Diff, additions are highlighted in green; +deletions are highlighted in red. diff --git a/docs/content/tutorial/step_00.ngdoc b/docs/content/tutorial/step_00.ngdoc index e506fcaf..a6dc1ca9 100755 --- a/docs/content/tutorial/step_00.ngdoc +++ b/docs/content/tutorial/step_00.ngdoc @@ -1,77 +1,90 @@ -@workInProgress
-@ngdoc overview
-@name Tutorial: Step 0
-@description
-
-<table id="tutorial_nav">
-<tr>
-<td id="previous_step">{@link tutorial Previous}</td>
-<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-0/app Example}</td>
-<td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">Code Diff</td>
-<td id="next_step">{@link tutorial.step_01 Next}</td>
-</tr>
-</table>
-
-The following sample code is our starting point. It is a static HTML page that displays next to
-nothing, but it has everything we need to proceed. You can think of this bit of code as our
-prototype template, consisting of basic HTML tags with a pair of angular specific attributes.
-
-__`app/index.html`:__
-<pre>
-<!doctype html>
-<html xmlns:ng="http://angularjs.org/">
-<head>
- <meta charset="utf-8">
- <title>my angular app</title>
- <link rel="stylesheet" href="css/app.css"/>
-</head>
-<body>
-
- Nothing here yet!
-
- <script src="lib/angular/angular.js" ng:autobind></script>
-</body>
-</html>
-</pre>
-
-## Discussion:
-
-Although our app doesn't appear to do anything dynamic, note the following:
-
-* __... `xmlns:ng="http://angularjs.org"` ...__ This `xmlns` declaration for the `ng` namespace
-must be specified if you use XHTML, or if you are targeting IE older than 9 (regardless of whether
-you are using XHTML or HTML).
-
-* __`<script src="lib/angular/angular.js"` ...__ This downloads the `angular.js` script and
-registers a callback that will be executed by the browser when the containing HTML page is fully
-downloaded. When the callback is executed, angular looks for the {@link
-angular.directive.ng:autobind ng:autobind} attribute. If `ng:autobind` is found, it signals
-angular to bootstrap and compile and manage the whole html page.
-
- Note: If you elected not to download any tutorial files but still want to try out some angular
- code on your system, you can change the relative path to the `angular.js` script in your
- template from `./lib/angular/angular.js` to the following:
-
- <script src="http://code.angularjs.org/angular-0.9.14.js" ng:autobind></script>
-
- This will download the angular script from the angular server instead of from a local file.
-
-* To try this code out in your browser, you need to navigate to the step-0 page (you are currently
-on Step 0 of the tutorial). If your http server is running, navigate to `app/index.html`.
-Remember, this is a relative URL (see the Relative URL section in {@link tutorial Tutorial}). The
-browser will display the same thing as you would see if you go to
-http://angular.github.com/angular-phonecat/step-0/app (accessible from Example link at the bottom
-of the page).
-
-Now we can move on and add some content to our developing web app.
-
-<table id="tutorial_nav">
-<tr>
-<td id="previous_step">{@link tutorial Previous}</td>
-<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-0/app Example}</td>
-<td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">Code Diff</td>
-<td id="next_step">{@link tutorial.step_01 Next}</td>
-</tr>
-</table>
+@ngdoc overview +@name Tutorial: Step 0 +@description + +<table id="tutorial_nav"> +<tr> +<td id="previous_step">{@link tutorial Previous}</td> +<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-0/app Live Demo}</td> +<td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">Code Diff</td> +<td id="next_step">{@link tutorial.step_0 Next}</td> +</tr> +</table> + +You are now ready to build the phone cat application. In this step, you will become familiar with +the most important source code files, learn how to start the web services, and run the application +in the browser. + +1. Do one of the following: + + * Git users: In the `angular-phonecat` directory, run this command: + + git checkout step-0 + + * Snapshot users: In the `[install directory]/sandbox` directory, run this command: + + ./goto_step.sh 0 + +This resets your workspace to Step 0 of the tutorial app. + +2. To see the app running in a browser, do one of the following: + * __For node.js users:__ + 1. In a _separate_ terminal tab or window, run `./scripts/web-server.js` to start the app + server. + 2. Open a browser window for the app and navigate to http://localhost:8000/app/index.html. + + * __For other http servers:__ + 1. Configure the server to serve the files in the `angular-phonecat` directory. + 2. Run `./scripts/web-server.js` to start the app server. + 3. Navigate in your browser to + http://localhost:[*port-number*]/[*context-path*]/app/index.html. + +You can now see the app in the browser. It's not very exciting, but that's OK. + +The code that created this app is shown below. You will see that it creates a static HTML page +that displays "Nothing here yet!"; the code does, however, have everything we need to proceed. +This bit of code serves as a prototype template, consisting of basic HTML tags with a pair of +angular-specific attributes. + +__`app/index.html`:__ +<pre> +<!doctype html> +<html xmlns:ng="http://angularjs.org/"> +<head> + <meta charset="utf-8"> + <title>my angular app</title> + <link rel="stylesheet" href="css/app.css"/> +</head> +<body> + + Nothing here yet! + + <script src="lib/angular/angular.js" ng:autobind></script> +</body> +</html> +</pre> + +## What is the code doing? + +* __... `xmlns:ng="http://angularjs.org"` ...__ This `xmlns` declaration for the `ng` namespace +must be specified in all angular applications if you use XHTML, or if you are targeting IE +versions older than 9 (regardless of whether you are using XHTML or HTML). + +* __`<script src="lib/angular/angular.js"` ...__ This downloads the `angular.js` script and +registers a callback that will be executed by the browser when the containing HTML page is fully +downloaded. When the callback is executed, angular looks for the {@link +angular.directive.ng:autobind ng:autobind} attribute. If `ng:autobind` is found, it signals +angular to bootstrap and compile and manage the whole html page. + +Now let's go to Step 1 and add some content to the web app. + +<table id="tutorial_nav"> +<tr> +<td id="previous_step">{@link tutorial Previous}</td> +<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-0/app Live Demo}</td> +<td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">Code Diff</td> +<td id="next_step">{@link tutorial.step_01 Next}</td> +</tr> +</table> diff --git a/docs/content/tutorial/step_01.ngdoc b/docs/content/tutorial/step_01.ngdoc index e22adc20..b9c171d2 100755 --- a/docs/content/tutorial/step_01.ngdoc +++ b/docs/content/tutorial/step_01.ngdoc @@ -1,88 +1,69 @@ -@workInProgress
-@ngdoc overview
-@name Tutorial: Step 1
-@description
-<table id="tutorial_nav">
- <tr>
- <td id="previous_step">{@link tutorial.step_00 Previous}</td>
- <td id="step_result">{@link http://angular.github.com/angular-phonecat/step-1/app Example}</td>
- <td id="tut_home">{@link tutorial Tutorial Home}</td>
- <td id="code_diff">
-{@link https://github.com/angular/angular-phonecat/compare/step-0...step-1 Code Diff}</td>
- <td id="next_step">{@link tutorial.step_02 Next}</td>
- </tr>
-</table>
-
-Now that we have the basic ingredients in place, let's add some basic information about two cell
-phones to our app.
-
-Note: We will usually include only the new code that we added for each step. In this and
-subsequent examples, we will leave out code from the previous step that hasn't changed, for
-example:
-
- ...
- <html xmlns:ng="http://angularjs.org">
- ...
-
-Let's add the following code to `index.html`:
-
-__`app/index.html`:__
-<pre>
-<head>
-...
- <title>Google Phone Gallery</title>
-...
-</head>
-...
- <ul>
- <li>
- <span>Nexus S<span>
- <p>
- Fast just got faster with Nexus S.
- </p>
- </li>
- <li>
- <span>Motorola XOOM™ with Wi-Fi<span>
- <p>
- The Next, Next Generation tablet.
- </p>
- </li>
- </ul>
-...
-</pre>
-
-## Discussion:
-
-* It's a static web page! We displayed info about two phones! Yay.
-
-* For those of you playing along at home on your own web servers, did you switch to Step 1 and
-refresh your browsers?
-
- * __{@link tutorial Using Git:}__
-
- From your `angular-phonecat` directory, run this command:
-
- git checkout step-1
-
- * __{@link tutorial Using Snapshots:}__
-
- From `[install directory]/sandbox`, run this command:
-
- ./goto_step.sh 1
-
-* Now would be a good time to open up `app/index.html` in your browser and see the current state
-of our "application". It's not very exciting, but that's ok.
-
-When you're ready, let's move on and start using some angular features to turn this static page
-into a dynamic web app.
-
-<table id="tutorial_nav">
- <tr>
- <td id="previous_step">{@link tutorial.step_00 Previous}</td>
- <td id="step_result">{@link http://angular.github.com/angular-phonecat/step-1/app Example}</td>
- <td id="tut_home">{@link tutorial Tutorial Home}</td>
- <td id="code_diff">
-{@link https://github.com/angular/angular-phonecat/compare/step-0...step-1 Code Diff}</td>
- <td id="next_step">{@link tutorial.step_02 Next}</td>
- </tr>
-</table>
+@ngdoc overview +@name Tutorial: Step 1 +@description +<table id="tutorial_nav"> + <tr> + <td id="previous_step">{@link tutorial.step_00 Previous}</td> + <td id="step_result">{@link http://angular.github.com/angular-phonecat/step-1/app Live + Demo}</td> + <td id="tut_home">{@link tutorial Tutorial Home}</td> + <td id="code_diff"> +{@link https://github.com/angular/angular-phonecat/compare/step-0...step-1 Code Diff}</td> + <td id="next_step">{@link tutorial.step_02 Next}</td> + </tr> +</table> + +In this step you will add some basic information about two cell phones to our app. + +1. Do one of the following to reset your workspace to Step 1; be aware that this will throw away +any changes you might have made to the tutorial files: + + * Git users run: + + git checkout --force step-1 + + * Snapshot users run: + + ./goto_step.sh 1 + +2. Refresh your browser or check the app out on {@link +http://angular.github.com/angular-phonecat/step-1/app, our server}. Your page now contains a list +with information about two phones. + +The most important changes are listed below. You can see the full diff on {@link +https://github.com/angular/angular-phonecat/compare/step-0...step-1 GitHub}: + +__`app/index.html`:__ +<pre> +... + <ul> + <li> + <span>Nexus S<span> + <p> + Fast just got faster with Nexus S. + </p> + </li> + <li> + <span>Motorola XOOM™ with Wi-Fi<span> + <p> + The Next, Next Generation tablet. + </p> + </li> + </ul> +... +</pre> + +This addition to your app uses static HTML to display the list. Now, let's go to Step 2 to learn +how to use angular to dynamically generate the same list. + +<table id="tutorial_nav"> + <tr> + <td id="previous_step">{@link tutorial.step_00 Previous}</td> + <td id="step_result">{@link http://angular.github.com/angular-phonecat/step-1/app Live + Demo}</td> + <td id="tut_home">{@link tutorial Tutorial Home}</td> + <td id="code_diff"> +{@link https://github.com/angular/angular-phonecat/compare/step-0...step-1 Code Diff}</td> + <td id="next_step">{@link tutorial.step_02 Next}</td> + </tr> +</table> diff --git a/docs/content/tutorial/step_02.ngdoc b/docs/content/tutorial/step_02.ngdoc index 50fbd240..143e310a 100755 --- a/docs/content/tutorial/step_02.ngdoc +++ b/docs/content/tutorial/step_02.ngdoc @@ -1,137 +1,174 @@ -@workInProgress
-@ngdoc overview
-@name Tutorial: Step 2
-@description
-<table id="tutorial_nav">
- <tr>
- <td id="previous_step">{@link tutorial.step_01 Previous}</td>
- <td id="step_result">{@link http://angular.github.com/angular-phonecat/step-2/app Example}</td>
- <td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-1...step-2 Code
-Diff}</td>
- <td id="next_step">{@link tutorial.step_03 Next}</td>
- </tr>
-</table>
-
-In the last step, we remembered what a basic, static web page looks like, and now we want to get
-dynamic. There are many ways to do this, but an important feature of angular is the incorporation
-of the principles behind {@link http://en.wikipedia.org/wiki/Model–View–Controller the MVC design
-pattern} into client-side web apps. With that in mind, let's use a little angular and a little
-JavaScript to add Model, View, and Controller components to our app, and change the static page
-into one that is dynamically generated.
-
-Our __View__ component is constructed by angular from this template:
-
-__`app/index.html`:__
-<pre>
-...
-<body ng:controller="PhoneListCtrl">
-
- <ul>
- <li ng:repeat="phone in phones">
- {{phone.name}}
- <p>{{phone.snippet}}</p>
- </li>
- </ul>
-
- <script src="lib/angular/angular.js" ng:autobind></script>
- <script src="js/controllers.js"></script>
-</body>
-...
-</pre>
-
-Our data __Model__ (a short list of phones in object literal notation) is instantiated within our
-__Controller__ function (`PhoneListCtrl`):
-
-__`app/js/controllers.js`:__
-<pre>
-/* App Controllers */
-
-function PhoneListCtrl() {
- this.phones = [{"name": "Nexus S",
- "snippet": "Fast just got faster with Nexus S."},
- {"name": "Motorola XOOM™ with Wi-Fi",
- "snippet": "The Next, Next Generation tablet."},
- {"name": "MOTOROLA XOOM™",
- "snippet": "The Next, Next Generation tablet."}];
-}
-</pre>
-
-The "Angular way" urges us to test as we develop:
-
-__`test/unit/controllersSpec.js`:__
-<pre>
-/* jasmine specs for controllers go here */
-describe('PhoneCat controllers', function() {
-
- describe('PhoneListCtrl', function(){
-
- it('should create "phones" model with 3 phones', function() {
- var ctrl = new PhoneListCtrl();
- expect(ctrl.phones.length).toBe(3);
- });
- });
-});
-</pre>
-
-## Discussion:
-
-So what were our changes from Step 1?
-
-* __View template:__ We replaced the hard-coded phone list with the {@link
-angular.widget.@ng:repeat ng:repeat widget} and two {@link guide.expression angular expressions}
-enclosed in curly braces: `{{phone.name}}` and `{{phone.snippet}}`:
-
- * The `ng:repeat="phone in phones"` statement in the `<li>` tag is an angular repeater. It
- tells angular to create a `<li>` element for each phone in the phones list, using the first
- `<li>` tag as the template.
-
- * The curly braces around `phone.name` and `phone.snippet` are an example of {@link
- angular.markup angular markup}. The curly braces are shorthand for the angular directive
- {@link angular.directive.ng:bind ng:bind}. They indicate to angular that these are template
- binding points. Binding points are locations in the template where angular creates
- data-binding between the View and the Model. In angular, the View is a projection of the Model
- through the HTML template. This means that whenever the model changes, angular refreshes the
- appropriate binding points, which updates the view.
-
-* __Controller:__ At this point, it doesn't appear as if our controller is doing very much
-controlling, but it is playing a crucial role: providing context for our data model so we can
-establish data-binding between the model and the view. Note in the following how we connected the
-dots between our presentation, data, and logic components:
-
- * The name of our controller function (in the JavaScript file `controllers.js`) matches the
- {@link angular.directive.ng:controller ng:controller} directive in the `<body>` tag
- (`PhoneListCtrl`).
- * We instantiated our data within the scope of our controller function, and our template
- binding points are located within the block bounded by the `<body
- ng:controller="PhoneListCtrl>` tag.
-
- Angular uses scopes, along with the information contained in the template, data model, and
- controller to keep the Model and View separated but in sync: any changes to the model are
- reflected in the view; any changes that occur in the view are reflected in the model.
-
-* __Model:__ For our data model, we created a simple array of phone records, specified in object
-literal notation.
-
-* __Testing:__ Ease of testing is another cornerstone of angular's design philosophy. All we are
-doing here is showing how easy it is to create a unit test using the technology baked into
-angular. The test verifies that we have 3 records in the phones array.
-
- To run this test, make sure you have a {@link tutorial test server running}, and type
- `./scripts/test.sh` from the command line.
-
- Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework
- when writing tests. So while Jasmine is not required by angular, we use it to write all tests
- in this tutorial. You can learn about Jasmine on the {@link http://pivotal.github.com/jasmine/
- Jasmine home page} and on the {@link https://github.com/pivotal/jasmine/wiki Jasmine wiki}.
-
-<table id="tutorial_nav">
- <tr>
- <td id="previous_step">{@link tutorial.step_01 Previous}</td>
- <td id="step_result">{@link http://angular.github.com/angular-phonecat/step-2/app Example}</td>
- <td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-1...step-2 Code
-Diff}</td>
- <td id="next_step">{@link tutorial.step_03 Next}</td>
- </tr>
-</table>
+@ngdoc overview +@name Tutorial: Step 2 +@description +<table id="tutorial_nav"> + <tr> + <td id="previous_step">{@link tutorial.step_01 Previous}</td> + <td id="step_result">{@link http://angular.github.com/angular-phonecat/step-2/app Live + Demo}</td> + <td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-1...step-2 Code +Diff}</td> + <td id="next_step">{@link tutorial.step_03 Next}</td> + </tr> +</table> + +Now it's time to make this web page dynamic with angular. 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. With angular, we encourage the use +of {@link http://en.wikipedia.org/wiki/Model–View–Controller the MVC design pattern} to decouple +the code and separate concerns. With that in mind, let's use a little angular and JavaScript to +add Model, View, and Controller components to our app. + +1. Reset your workspace to Step 2 using: + + git checkout --force step-2 + or + + ./goto_step.sh 2 + +2. Refresh your browser or check the app out on {@link +http://angular.github.com/angular-phonecat/step-2/app our server}. The app now contains a list +with 3 phones. + +The most important changes are listed below. You can see the full diff on {@link +https://github.com/angular/angular-phonecat/compare/step-1...step-2 GitHub}: + + +## Template for the View + +The __View__ component is constructed by angular from this template: + +__`app/index.html`:__ +<pre> +... +<body ng:controller="PhoneListCtrl"> + + <ul> + <li ng:repeat="phone in phones"> + {{phone.name}} + <p>{{phone.snippet}}</p> + </li> + </ul> + + <script src="lib/angular/angular.js" ng:autobind></script> + <script src="js/controllers.js"></script> +</body> +</html> +</pre> + +We replaced the hard-coded phone list with the {@link angular.widget.@ng:repeat ng:repeat widget} +and two {@link guide.expression angular expressions} enclosed in curly braces: `{{phone.name}}` +and `{{phone.snippet}}`: + + * The `ng:repeat="phone in phones"` statement in the `<li>` tag is an angular repeater. It + tells angular to create a `<li>` element for each phone in the phones list, using the first + `<li>` tag as the template. + + * The curly braces around `phone.name` and `phone.snippet` are an example of {@link + angular.markup angular markup}. The curly braces are shorthand for the angular directive + {@link angular.directive.ng:bind ng:bind}. They indicate to angular that these are template + binding points. Binding points are locations in the template where angular creates + data-binding between the View and the Model. In angular, the View is a projection of the Model + through the HTML template. This means that whenever the model changes, angular refreshes the + appropriate binding points, which updates the view. + + +## Model and Controller + +The data __Model__ (a short list of phones in object literal notation) is instantiated within the +__Controller__ function (`PhoneListCtrl`): + +__`app/js/controllers.js`:__ +<pre> +function PhoneListCtrl() { + this.phones = [{"name": "Nexus S", + "snippet": "Fast just got faster with Nexus S."}, + {"name": "Motorola XOOM™ with Wi-Fi", + "snippet": "The Next, Next Generation tablet."}, + {"name": "MOTOROLA XOOM™", + "snippet": "The Next, Next Generation tablet."}]; +} +</pre> + +Although the controller is not yet doing very much controlling, it is playing a crucial role. By +providing context for our data model, the controller allows us to establish data-binding between +the model and the view. Note in the following how we connected the dots between our presentation, +data, and logic components: + + * The name of our controller function (in the JavaScript file `controllers.js`) matches the + {@link angular.directive.ng:controller ng:controller} directive in the `<body>` tag + (`PhoneListCtrl`). + * We instantiated our data within the scope of our controller function, and our template + binding points are located within the block bounded by the `<body + ng:controller="PhoneListCtrl>` tag. + + Angular uses scopes, along with the information contained in the template, data model, and + controller to keep the Model and View separated but in sync: any changes to the model are + reflected in the view; any changes that occur in the view are reflected in the model. + +As for our data model, we created a simple array of phone records, specified in object literal +notation. + +## Tests + +The "Angular way" makes it easy for us to test as we develop; the unit test for your newly created +controller looks as follows: + +__`test/unit/controllersSpec.js`:__ +<pre> +describe('PhoneCat controllers', function() { + + describe('PhoneListCtrl', function(){ + + it('should create "phones" model with 3 phones', function() { + var ctrl = new PhoneListCtrl(); + expect(ctrl.phones.length).toBe(3); + }); + }); +}); +</pre> + +Ease of testing is another cornerstone of angular's design philosophy. All we are doing here is +showing how easy it is to create a unit test. The test verifies that we have 3 records in the +phones array. + +Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework when +writing tests. Although Jasmine is not required by angular, we used it to write all tests in this +tutorial. You can learn about Jasmine on the {@link http://pivotal.github.com/jasmine/ Jasmine +home page} and on the {@link https://github.com/pivotal/jasmine/wiki Jasmine wiki}. + +angular-seed project is pre-configured to run all unit tests using {@link +http://code.google.com/p/js-test-driver/ JsTestDriver}. To run the test, do the following: + + 1. In a _separate_ terminal window or tab, go to the `angular-phonecat` directory and run + `./scripts/test-server.sh` to start the test web server. + + 2. Open a new browser tab or window, navigate to http://localhost:9876, and choose "strict + mode". At this point, you can leave this tab open and forget about it. JsTestDriver will + use it to execute our tests and report the results in the terminal. + + 3. Execute the test by running `./scripts/test.sh` + +You should see the following or similar output: + + Chrome: Runner reset. + . + Total 1 tests (Passed: 1; Fails: 0; Errors: 0) (2.00 ms) + Chrome 11.0.696.57 Mac OS: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (2.00 ms) + +Yay! The test passed! Now, let's go to Step 3 to learn how to add full text search to the app. + + +<table id="tutorial_nav"> + <tr> + <td id="previous_step">{@link tutorial.step_01 Previous}</td> + <td id="step_result">{@link http://angular.github.com/angular-phonecat/step-2/app Live + Demo}</td> + <td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-1...step-2 Code +Diff}</td> + <td id="next_step">{@link tutorial.step_03 Next}</td> + </tr> +</table> diff --git a/docs/content/tutorial/step_03.ngdoc b/docs/content/tutorial/step_03.ngdoc index 4333636d..812f58bb 100755 --- a/docs/content/tutorial/step_03.ngdoc +++ b/docs/content/tutorial/step_03.ngdoc @@ -1,108 +1,142 @@ -@workInProgress
-@ngdoc overview
-@name Tutorial: Step 3
-@description
-<table id="tutorial_nav">
-<tr>
- <td id="previous_step">{@link tutorial.step_02 Previous}</td>
- <td id="step_result">{@link http://angular.github.com/angular-phonecat/step-3/app Example}</td>
- <td id="tut_home">{@link tutorial Tutorial Home}</td>
- <td id="code_diff">{@link
- https://github.com/angular/angular-phonecat/commit/a03815f8fb00217f5f9c1d3ef83282f79818e706 Code
- Diff}</td>
- <td id="next_step">{@link tutorial.step_04 Next}</td>
-</tr>
-</table>
-
-We did a lot of work in laying the foundation of our app in the last step, so now we'll do
-something simple, and add full text search. We will also write an end-to-end 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.
-
-__`app/index.html`:__
-<pre>
-...
- Fulltext Search: <input name="query"/>
-
- <ul class="phones">
- <li ng:repeat="phone in phones.$filter(query)">
- {{phone.name}}
- <p>{{phone.snippet}}</p>
- </li>
- </ul>
-...
-</pre>
-__`test/e2e/scenarios.js`:__
-<pre>
-/* jasmine-like end2end tests go here */
-describe('PhoneCat App', function() {
-
- describe('Phone list view', function() {
-
- beforeEach(function() {
- browser().navigateTo('../../app/index.html');
- });
-
-
- it('should filter the phone list as user types into the search box', function() {
- expect(repeater('.phones li').count()).toBe(3);
-
- input('query').enter('nexus');
- expect(repeater('.phones li').count()).toBe(1);
-
- input('query').enter('motorola');
- expect(repeater('.phones li').count()).toBe(2);
- });
- });
-});
-</pre>
-
-## Discussion:
-
-We continued using the same controller that we set up in Step 2, but we added the following
-features to our app:
-
-* __Search Box:__ A standard HTML `<input>` tag combined with angular's {@link
-angular.Array.filter $filter} utility (added to the repeater) lets a user type in search criteria
-and immediately see the effects of their search on the phone list. This new code demonstrates the
-following:
-
- * Two way Data-binding. This is one of the core features in angular. When the page loads,
- angular binds the name of the input box to a variable of the same name in the data model and
- keeps the two in sync.
-
-In this example, the data that you type into the input box (named __`query`__) is immediately
-available as a filter input in the list repeater (`phone in phones.$filter(`__`query`__`)`).
-Whenever the data model changes and this change causes the input to the repeater to change, the
-repeater will efficiently update the DOM to reflect the current state of the model.
-
- * Use of `$filter` in a template. The `$filter` function is one of several built-in {@link
- angular.Array angular functions} that augment JavaScript arrays during their evaluation as
- angular expressions. In {@link guide.expression angular expressions}, these array utilities are
- available as array methods. (They are prefixed with a $ to avoid naming collisions.)
-
- * `ng:repeat` automatically shrinks and grows the number of phones in the View, via DOM
- manipulation that is completely transparent to the developer. If you've written any DOM
- manipulation code, this should make you happy.
-
-* __CSS:__ We added in some minimal CSS to the file we set up in Step 0: `./css/app.css`.
-
-* __Testing:__ To run the end to end test, open http://localhost:8000/test/e2e/runner.html in
-your browser. This end-to-end test shows the following:
-
- * Proof that the search box and the repeater are correctly wired together.
-
- * How easy it is to write end-to-end tests. This is just a simple test, but the point here is
- to show how easy it is to set up a functional, readable, end-to-end test.
-
-<table id="tutorial_nav">
-<tr>
- <td id="previous_step">{@link tutorial.step_02 Previous}</td>
- <td id="step_result">{@link http://angular.github.com/angular-phonecat/step-3/app Example}</td>
- <td id="tut_home">{@link tutorial Tutorial Home}</td>
- <td id="code_diff">{@link
- https://github.com/angular/angular-phonecat/commit/a03815f8fb00217f5f9c1d3ef83282f79818e706 Code
- Diff}</td>
- <td id="next_step">{@link tutorial.step_04 Next}</td>
-</tr>
-</table>
+@ngdoc overview +@name Tutorial: Step 3 +@description +<table id="tutorial_nav"> +<tr> + <td id="previous_step">{@link tutorial.step_02 Previous}</td> + <td id="step_result">{@link http://angular.github.com/angular-phonecat/step-3/app Live + Demo}</td> + <td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-2...step-3 Code +Diff}</td> + <td id="next_step">{@link tutorial.step_04 Next}</td> +</tr> +</table> + +We did a lot of work in laying a foundation for the app in the last step, so now we'll do +something simple, and add full text search (yes, it will be simple!). We will also write an +end-to-end 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. + +1. Reset your workspace to Step 3 using: + + git checkout --force step-3 + + or + + ./goto_step.sh 3 + +2. Refresh your browser or check the app out on {@link +http://angular.github.com/angular-phonecat/step-3/app our server}. The app now has a search box. +The phone list on the page changes depending on what a user types into the search box. + +The most important changes are listed below. You can see the full diff on {@link +https://github.com/angular/angular-phonecat/compare/step-2...step-3 + GitHub}: + + +## Controller + +We made no changes to the controller. + + +## Template + +__`app/index.html`:__ +<pre> +... + Fulltext Search: <input name="query"/> + + <ul class="phones"> + <li ng:repeat="phone in phones.$filter(query)"> + {{phone.name}} + <p>{{phone.snippet}}</p> + </li> + </ul> +... +</pre> + +We added a standard HTML `<input>` tag and use angular's {@link angular.Array.filter $filter} +function to process the input for the `ng:repeater`. + +This lets a user enter search criteria and immediately see the effects of their search on the +phone list. This new code demonstrates the following: + +* Data-binding. This is one of the core features in angular. When the page loads, angular binds +the name of the input box to a variable of the same name in the data model and keeps the two in +sync. + + In this code, the data that a user types into the input box (named __`query`__) is immediately + available as a filter input in the list repeater (`phone in phones.$filter(`__`query`__`)`). + When changes to the data model cause the repeater's input to change, the repeater efficiently + updates the DOM to reflect the current state of the model. + +* Use of `$filter`. The `{@link angular.Array.filter $filter}` method, uses the `query` value, to +create a new array that contains only those records that match the `query`. + +* `ng:repeat` automatically updates the view in response to the changing number of phones returned +by the `$filter`. The process is completely transparent to the developer. + +## Test + +In Step 2, we learned how to write and run unit tests. Unit tests are perfect for testing +controllers and other components of our application written in JavaScript, but they can't easily +test DOM manipulation or the wiring of our application. For these, an end-to-end test is a much +better choice. + +The search feature was fully implemented via templates and data-binding, so we'll write our first +end-to-end test, to verify that the feature works. + +__`test/e2e/scenarios.js`:__ +<pre> +describe('PhoneCat App', function() { + + describe('Phone list view', function() { + + beforeEach(function() { + browser().navigateTo('../../app/index.html'); + }); + + it('should filter the phone list as user types into the search box', function() { + expect(repeater('.phones li').count()).toBe(3); + + input('query').enter('nexus'); + expect(repeater('.phones li').count()).toBe(1); + + input('query').enter('motorola'); + expect(repeater('.phones li').count()).toBe(2); + }); + }); +}); +</pre> + +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 +https://docs.google.com/document/d/11L8htLKrh6c92foV71ytYpiKkeKpM4_a5-9c3HywfIc/edit?hl=en&pli=1# +angular's end-to-end test runner}. + +To run the end-to-end test, open the following in a new browser tab: + +* node.js users: http://localhost:8000/test/e2e/runner.html +* users with other http servers: +http://localhost:[*port-number*]/[*context-path*]/test/e2e/runner.html +* casual reader: http://angular.github.com/angular-phonecat/step-3/test/e2e/runner.html + +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. + +Now that you've verified everything, go to Step 4 to learn how to add sorting capability to the +phone list app. + +<table id="tutorial_nav"> +<tr> + <td id="previous_step">{@link tutorial.step_02 Previous}</td> + <td id="step_result">{@link http://angular.github.com/angular-phonecat/step-3/app Live + Demo}</td> + <td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-2...step-3 Code +Diff}</td> + <td id="next_step">{@link tutorial.step_04 Next}</td> +</tr> +</table> diff --git a/docs/content/tutorial/step_04.ngdoc b/docs/content/tutorial/step_04.ngdoc index 0589ba75..c1fd00bb 100755 --- a/docs/content/tutorial/step_04.ngdoc +++ b/docs/content/tutorial/step_04.ngdoc @@ -1,161 +1,201 @@ -@workInProgress
-@ngdoc overview
-@name Tutorial: Step 4
-@description
-<table id="tutorial_nav">
-<tr>
-<td id="previous_step">{@link tutorial.step_03 Previous}</td>
-<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-4/app Example}</td>
-<td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-3...step-4 Code
-Diff}</td>
-<td id="next_step">{@link tutorial.step_05 Next}</td>
-</tr>
-</table>
-
-In this step, we add a feature that lets our users choose which way to order the phone list.
-
-__`app/index.html`:__
-<pre>
-...
- <ul class="predicates">
- <li>
- Search: <input type="text" name="query"/>
- </li>
- <li>
- Sort by:
- <select name="orderProp">
- <option value="name">Alphabetical</option>
- <option value="age">Newest</option>
- </select>
- </li>
- </ul>
-
- <ul class="phones">
- <li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)">
- {{phone.name}}
- <p>{{phone.snippet}}</p>
- </li>
- </ul>
-...
-</pre>
-
-__`app/js/controller.js`:__
-<pre>
-/* App Controllers */
-
-function PhoneListCtrl() {
- this.phones = [{"name": "Nexus S",
- "snippet": "Fast just got faster with Nexus S.",
- "age": 0},
- {"name": "Motorola XOOM™ with Wi-Fi",
- "snippet": "The Next, Next Generation tablet.",
- "age": 1},
- {"name": "MOTOROLA XOOM™",
- "snippet": "The Next, Next Generation tablet.",
- "age": 2}];
-
- this.orderProp = 'age';
-}
-</pre>
-
-__`test/unit/controllerSpec.js`:__
-<pre>
-/* jasmine specs for controllers go here */
-describe('PhoneCat controllers', function() {
-
- describe('PhoneListCtrl', function(){
- var scope, $browser, ctrl;
-
- beforeEach(function() {
- ctrl = new PhoneListCtrl();
- });
-
-
- it('should create "phones" model with 3 phones', function() {
- expect(ctrl.phones.length).toBe(3);
- });
-
-
- it('should set the default value of orderProp model', function() {
- expect(ctrl.orderProp).toBe('age');
- });
- });
-});
-</pre>
-
-__`test/e2e/scenarios.js`:__
-<pre>
-/* jasmine-like end2end tests go here */
-describe('PhoneCat App', function() {
-
- describe('Phone list view', function() {
-
- beforeEach(function() {
- browser().navigateTo('../../app/index.html');
- });
-
-
- it('should filter the phone list as user types into the search box', function() {
- expect(repeater('.phones li').count()).toBe(3);
-
- input('query').enter('nexus');
- expect(repeater('.phones li').count()).toBe(1);
-
- input('query').enter('motorola');
- expect(repeater('.phones li').count()).toBe(2);
- });
-
-
- it('should be possible to control phone order via the drop down select box', function() {
- input('query').enter('tablet'); //let's narrow the dataset to make the test assertions
- shorter
-
- expect(repeater('.phones li', 'Phone List').column('a')).
- toEqual(["Motorola XOOM\u2122 with Wi-Fi",
- "MOTOROLA XOOM\u2122"]);
-
- select('orderProp').option('alphabetical');
-
- expect(repeater('.phones li', 'Phone List').column('a')).
- toEqual(["MOTOROLA XOOM\u2122",
- "Motorola XOOM\u2122 with Wi-Fi"]);
- });
- });
-});
-</pre>
-
-## Discussion:
-
-To provide dynamic ordering, we employ another one of angular's "array type augmenters" and let
-the data binding do the rest of the work for us:
-
-* First, we provide a `<select>` element named `orderProp` for our users so they can choose to
-sort the phone list either alphabetically or by the age of the phone. We added the `age` property
-to each phone record so we can sort by that field.
-
-* Like {@link angular.Array.filter $filter}, {@link angular.Array.orderBy $orderBy} is a built-in
-method available on array objects in angular expressions. In our UI template, we set up a select
-box that lets the user set the `orderProp` model variable to one of the string constants: `age` or
-`name`.
-
-* In our controller, we added a line to set the default value of `orderProp` to `age`. If we
-don't override the default value, angular uses the value of the first `<option>` element when it
-initializes the data model.
-
-* Our unit test now verifies that our default ordering property is set.
-
-* We added an end-to-end test to verify that our select box ordering mechanism works properly.
-
-* Once again we added a little more CSS to improve the View.
-
-<table id="tutorial_nav">
-<tr>
-<td id="previous_step">{@link tutorial.step_03 Previous}</td>
-<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-4/app Example}</td>
-<td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-3...step-4 Code
-Diff}</td>
-<td id="next_step">{@link tutorial.step_05 Next}</td>
-</tr>
-</table>
+@ngdoc overview +@name Tutorial: Step 4 +@description +<table id="tutorial_nav"> +<tr> +<td id="previous_step">{@link tutorial.step_03 Previous}</td> +<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-4/app Live Demo}</td> +<td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-3...step-4 Code +Diff}</td> +<td id="next_step">{@link tutorial.step_05 Next}</td> +</tr> +</table> + +In this step, you will add a feature to let your users select the order of the items in the phone +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. + + +1. Reset your workspace to Step 4 using: + + git checkout --force step-4 + +or + + ./goto_step.sh 4 + +2. Refresh your browser or check the app out on {@link +http://angular.github.com/angular-phonecat/step-4/app our server}. 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 changes are listed below. You can see the full diff on {@link +https://github.com/angular/angular-phonecat/compare/step-3...step-4 +GitHub}: + + +## Template + +__`app/index.html`:__ +<pre> +... + <ul class="predicates"> + <li> + Search: <input type="text" name="query"/> + </li> + <li> + Sort by: + <select name="orderProp"> + <option value="name">Alphabetical</option> + <option value="age">Newest</option> + </select> + </li> + </ul> + + <ul class="phones"> + <li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)"> + {{phone.name}} + <p>{{phone.snippet}}</p> + </li> + </ul> +... +</pre> + +In the `index.html` template we made the following changes: + +* First, we added a `<select>` html element named `orderProp`, so that our users can pick from the +two provided sorting options. + +* We then chained the `$filter` method with `{@link angular.Array.orderBy $orderBy}` method to +further process the input into the repeater. + +Angular creates a two way data-binding between the select element and the `orderProp` model. +`orderProp` is then used as the input for the `$orderBy` method. + +As we discussed in the section about data-binding and the repeater in Step 3, whenever the model +changes (for example because a user changes the order with the select drop down menu), angular's +data-binding will cause the view to automatically update. No bloated DOM manipulation code is +necessary! + + + +## Controller + +__`app/js/controller.js`:__ +<pre> +/* App Controllers */ + +function PhoneListCtrl() { + this.phones = [{"name": "Nexus S", + "snippet": "Fast just got faster with Nexus S.", + "age": 0}, + {"name": "Motorola XOOM™ with Wi-Fi", + "snippet": "The Next, Next Generation tablet.", + "age": 1}, + {"name": "MOTOROLA XOOM™", + "snippet": "The Next, Next Generation tablet.", + "age": 2}]; + + this.orderProp = 'age'; +} +</pre> + +* We modified the `phones` model - the array of phones - and added an `age` property to each phone +record. This property is used to order phones by age. + +* We added a line to the controller that sets the default value of `orderProp` to `age`. If we had +not set the default value here, angular would have used the value of the first `<option>` element +(`'name'`) when it initialized the data model. + + + + +## Test + +The changes we made should be verified with both a unit test and an end-to-end test. Let's look at +the unit test first. + +__`test/unit/controllerSpec.js`:__ +<pre> +/* jasmine specs for controllers go here */ +describe('PhoneCat controllers', function() { + + describe('PhoneListCtrl', function(){ + var scope, $browser, ctrl; + + beforeEach(function() { + ctrl = new PhoneListCtrl(); + }); + + + it('should create "phones" model with 3 phones', function() { + expect(ctrl.phones.length).toBe(3); + }); + + + it('should set the default value of orderProp model', function() { + expect(ctrl.orderProp).toBe('age'); + }); + }); +}); +</pre> + + +The unit test now verifies that the default ordering property is set. + +We used Jasmine's API to extract the controller construction into a `beforeEach` block, which is +shared by all tests in the nearest `describe` block. + +To run the unit tests, once again execute the `./scripts/test.sh` script and you should see the +following output. + + Chrome: Runner reset. + .. + Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (3.00 ms) + Chrome 11.0.696.57 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms) + + +Let's turn our attention to the end-to-end test. + +__`test/e2e/scenarios.js`:__ +<pre> +... + it('should be possible to control phone order via the drop down select box', function() { + input('query').enter('tablet'); //let's narrow the dataset to make the test assertions + shorter + + expect(repeater('.phones li', 'Phone List').column('a')). + toEqual(["Motorola XOOM\u2122 with Wi-Fi", + "MOTOROLA XOOM\u2122"]); + + select('orderProp').option('alphabetical'); + + expect(repeater('.phones li', 'Phone List').column('a')). + toEqual(["MOTOROLA XOOM\u2122", + "Motorola XOOM\u2122 with Wi-Fi"]); + }); +... +</pre> + +The end-to-end test verifies that the ordering mechanism of the select box is working correctly. + +You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you +can see them running on {@link +http://angular.github.com/angular-phonecat/step-4/test/e2e/runner.html +angular's server}. + +Now that you have added list sorting and tested the app, go to Step 5 to learn about angular +services and how angular uses dependency injection. + + +<table id="tutorial_nav"> +<tr> +<td id="previous_step">{@link tutorial.step_03 Previous}</td> +<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-4/app Live Demo}</td> +<td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-3...step-4 Code +Diff}</td> +<td id="next_step">{@link tutorial.step_05 Next}</td> +</tr> +</table> diff --git a/docs/content/tutorial/step_05.ngdoc b/docs/content/tutorial/step_05.ngdoc index 8ec0fca4..0c8f0dde 100755 --- a/docs/content/tutorial/step_05.ngdoc +++ b/docs/content/tutorial/step_05.ngdoc @@ -1,147 +1,219 @@ -@workInProgress
-@ngdoc overview
-@name Tutorial: Step 5
-@description
-<table id="tutorial_nav">
-<tr>
- <td id="previous_step">{@link tutorial.step_04 Previous}</td>
- <td id="step_result">{@link http://angular.github.com/angular-phonecat/step-5/app Example}</td>
- <td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-4...step-5 Code
-Diff}</td>
- <td id="next_step">{@link tutorial.step_06 Next}</td>
-</tr>
-</table>
-
-In this step, the View template remains the same but the Model and Controller change. We'll
-introduce the use of an angular {@link angular.service service}, which we will use to implement an
-`XMLHttpRequest` request to communicate with a server. Angular provides the built-in {@link
-angular.service.$xhr $xhr} service to make this easy.
-
-The addition of the `$xhr` service to our app gives us the opportunity to talk about {@link
-guide.di Dependency Injection} (DI). The use of DI is another cornerstone of the angular
-philosophy. DI helps make your web apps well structured, loosely coupled, and ultimately easier to
-test.
-
-__`app/js/controllers.js:`__
-<pre>
-/* App Controllers */
-
-function PhoneListCtrl($xhr) {
- var self = this;
-
- $xhr('GET', 'phones/phones.json', function(code, response) {
- self.phones = response;
- });
-
- self.orderProp = 'age';
-}
-
-//PhoneListCtrl.$inject = ['$xhr'];
-</pre>
-
-__`test/unit/controllerSpec.js`:__
-<pre>
-/* jasmine specs for controllers go here */
-describe('PhoneCat controllers', function() {
-
- describe('PhoneListCtrl', function(){
- var scope, $browser, ctrl;
-
- beforeEach(function() {
- scope = angular.scope();
- $browser = scope.$service('$browser');
-
- $browser.xhr.expectGET('phones/phones.json').respond([{name: 'Nexus S'},
- {name: 'Motorola DROID'}]);
- ctrl = scope.$new(PhoneListCtrl);
- });
-
-
- it('should create "phones" model with 2 phones fetched from xhr', function() {
- expect(ctrl.phones).toBeUndefined();
- $browser.xhr.flush();
-
- expect(ctrl.phones).toEqual([{name: 'Nexus S'},
- {name: 'Motorola DROID'}]);
- });
-
-
- it('should set the default value of orderProp model', function() {
- expect(ctrl.orderProp).toBe('age');
- });
- });
-});
-</pre>
-
-## Discussion:
-
-* __Services:__ {@link angular.service Services} are substitutable objects managed by angular's
-{@link guide.di DI subsystem}. Angular services simplify some of the standard operations common
-to web apps. Angular provides several built-in services (such as {@link angular.service.$xhr
-$xhr}). You can also create your own custom services.
-
-* __Dependency Injection:__ To use an angular service, you simply provide the name of the service
-as an argument to the controller's constructor function. The name of the argument is significant,
-because angular's {@link guide.di DI subsystem} recognizes the identity of a service by its name,
-and provides the name of the service to the controller during the controller's construction. The
-dependency injector also takes care of creating any transitive dependencies the service may have
-(services often depend upon other services).
-
- Note: if you minify the javascript code for this controller, all function arguments will be
- minified as well. This will result in the dependency injector not being able to identify
- services correctly. To overcome this issue, just assign an array with service identifier strings
- into the `$inject` property of the controller function.
-
-* __`$xhr`:__ We moved our data set out of the controller and into the file
-`app/phones/phones.json` (and added some more phones). We used the `$xhr` service to make a GET
-HTTP request to our web server, asking for `phone/phones.json` (the url is relative to our
-`index.html` file). The server responds with the contents of the json file, which serves as the
-source of our data. Keep in mind that the response might just as well have been dynamically
-generated by a sophisticated backend server. To our web server they both look the same, but using
-a real backend server to generate a response would make our tutorial unnecessarily complicated.
-
- Notice that the $xhr service takes a callback as the last parameter. This callback is used to
- process the response. In our case, we just assign the response to the current scope controlled
- by the controller, as a model called `phones`. Have you realized that we didn't even have to
- parse the response? Angular took care of that for us.
-
-* __Testing:__ The unit tests have been expanded. Because of the dependency injection business,
-we now need to create the controller the same way that angular does it behind the scenes. For this
-reason, we need to:
-
- * Create a root scope object by calling `angular.scope()`
-
- * Call `scope.$new(PhoneListCtrl)` to get angular to create the child scope associated with
- our controller.
-
- At the same time, we need to tell the testing harness that it should expect an incoming
- request from our controller. To do this we:
-
- * Use the `$service` method to retrieve the `$browser` service - this is a service that in
- angular represents various browser APIs. In tests, angular automatically uses a mock version
- of this service that allows you to write tests without having to deal with these native APIs
- and the global state associated with them.
-
- * We use the `$browser.expectGET` method to train the `$browser` object to expect an incoming
- http request and tell it what to respond with. Note that the responses are not returned before
- we call the `$browser.xhr.flush()` method.
-
- * We then make assertions to verify that the `phones` model doesn't exist on the scope, before
- the response is received.
-
- * We flush the xhr queue in the browser by calling `$browser.xhr.flush()`. This causes the
- callback we passed into the `$xhr` service to be executed with the trained response.
-
- * Finally, we make the assertions, verifying that the phone model now exists on the scope.
-
-<table id="tutorial_nav">
-<tr>
- <td id="previous_step">{@link tutorial.step_04 Previous}</td>
- <td id="step_result">{@link http://angular.github.com/angular-phonecat/step-5/app Example}</td>
- <td id="tut_home">{@link tutorial Tutorial Home}</td>
- <td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-4...step-5
- Code Diff}</td>
- <td id="next_step">{@link tutorial.step_06 Next}</td>
-</tr>
-</table>
+@ngdoc overview +@name Tutorial: Step 5 +@description +<table id="tutorial_nav"> +<tr> + <td id="previous_step">{@link tutorial.step_04 Previous}</td> + <td id="step_result">{@link http://angular.github.com/angular-phonecat/step-5/app Live Demo +}</td> + <td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-4...step-5 Code +Diff}</td> + <td id="next_step">{@link tutorial.step_06 Next}</td> +</tr> +</table> + +Enough of building an app with three phones in a hard-coded dataset! Let's fetch a larger dataset +from our server using one of angular's built-in {@link angular.service services} called {@link +angular.service.$xhr $xhr}. We will use angular's dependency injection to provide the service to +the `PhoneListCtrl` controller. + +1. Reset your workspace to Step 5 using: + + git checkout --force step-5 + +or + + ./goto_step.sh 5 + +2. Refresh your browser or check the app out on {@link +http://angular.github.com/angular-phonecat/step-5/app our server}. You should now see a list of 20 +phones. + + +The most important changes are listed below. You can see the full diff on {@link +https://github.com/angular/angular-phonecat/compare/step-4...step-5 +GitHub}: + +## Data + +The `app/phones/phone.json` file in your project is a dataset that contains a larger list of +phones stored in the JSON format. + +Following is a sample of the file: +<pre> +[ + { + "age": 13, + "id": "motorola-defy-with-motoblur", + "name": "Motorola DEFY\u2122 with MOTOBLUR\u2122", + "snippet": "Are you ready for everything life throws your way?" + ... + }, +... +] +</pre> + + +## Controller + +In this step, the view template will remain the same but the model and controller will change. +We'll use angular's {@link angular.service.$xhr} service to make an HTTP request to your web +server to fetch the data in the `phones.json` file. + +__`app/js/controllers.js:`__ +<pre> +function PhoneListCtrl($xhr) { + var self = this; + + $xhr('GET', 'phones/phones.json', function(code, response) { + self.phones = response; + }); + + self.orderProp = 'age'; +} + +//PhoneListCtrl.$inject = ['$xhr']; +</pre> + +We removed the hard-coded dataset from the controller and instead are using the `$xhr` service to +access the data stored in `app/phones/phones.json`. The `$xhr` service makes a HTTP GET request to +our web server, asking for `phone/phones.json` (the url is relative to our `index.html` file). The +server responds by providing the data in the json file. + +Keep in mind that the response might just as well have been dynamically generated by a backend +server. To the browser and our app they both look the same. For the sake of simplicity we used a +json file in this tutorial. + +Notice that the `$xhr` service takes a callback as the last parameter. This callback is used to +process the response. In our case, we just assign the response to the current scope controlled by +the controller, as a model called `phones`. Have you realized that we didn't even have to parse +the response? Angular took care of that for us. + +We already mentioned that the `$xhr` function we just used is an angular service. {@link +angular.service Angular services} are substitutable objects managed by angular's {@link guide.di +DI subsystem}. + +Dependency injection helps to make your web apps well structured, loosely coupled, and much easier +to test. What's important to understand is how the controllers get access to these services +through dependency injection. + +The dependency injection pattern is based on declaring the dependencies we require and letting the +system provide them to us. To do this in angular, you simply provide the names of the services you +need as arguments to the controller's constructor function, as follows: + + function PhoneListCtrl($xhr) { + +The name of the argument is significant, because angular recognizes the identity of a service by +the argument name. Once angular knows what services are being requested, it provides them to the +controller when the controller is being constructed. The dependency injector also takes care of +creating any transitive dependencies the service may have (services often depend upon other +services). + +As we mentioned earlier, angular infers the controller's dependencies from the names of arguments +of the controller's constructor function. If you were to minify the JavaScript code for this +controller, all of these function arguments would be minified as well, and the dependency injector +would not being able to identify services correctly. + +To overcome issues caused by minification, just assign an array with service identifier strings +into the `$inject` property of the controller function, just like the last line in the snippet +(commented out) suggests: + + PhoneListCtrl.$inject = ['$xhr']; + + +## Test + +__`test/unit/controllersSpec.js`:__ +<pre> +describe('PhoneCat controllers', function() { + + describe('PhoneListCtrl', function(){ + var scope, $browser, ctrl; + + beforeEach(function() { + scope = angular.scope(); + $browser = scope.$service('$browser'); + + $browser.xhr.expectGET('phones/phones.json').respond([{name: 'Nexus S'}, + {name: 'Motorola DROID'}]); + ctrl = scope.$new(PhoneListCtrl); + }); + + + it('should create "phones" model with 2 phones fetched from xhr', function() { + expect(ctrl.phones).toBeUndefined(); + $browser.xhr.flush(); + + expect(ctrl.phones).toEqual([{name: 'Nexus S'}, + {name: 'Motorola DROID'}]); + }); + + + it('should set the default value of orderProp model', function() { + expect(ctrl.orderProp).toBe('age'); + }); + }); +}); +</pre> + + +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 `$xhr` 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. + +To create the controller in the test environment, do the following: + + * Create a root scope object by calling `angular.scope()` + + * Call `scope.$new(PhoneListCtrl)` to get angular to create the child scope associated with + the `PhoneListCtrl` controller. + +Because our code now uses the `$xhr` service to fetch the phone list data in our controller, +before we create the `PhoneListCtrl` child scope, we need to tell the testing harness to expect an +incoming request from the controller. To do this we: + + * Use the `{@link angular.scope.$service $service}` method to retrieve the `$browser` service, + a service that angular uses to represent various browser APIs. In tests, angular automatically + uses a mock version of this service that allows you to write tests without having to deal with + these native APIs and the global state associated with them. + + * We use the `$browser.expectGET` method to train the `$browser` object to expect an incoming + HTTP request and tell it what to respond with. Note that the responses are not returned before + we call the `$browser.xhr.flush()` method. + + * We then make assertions to verify that the `phones` model doesn't exist on the scope, before + the response is received. + + * We flush the xhr queue in the browser by calling `$browser.xhr.flush()`. This causes the + callback we passed into the `$xhr` service to be executed with the trained response. + + * Finally, we make the assertions, verifying that the phone model now exists on the scope. + +To run the unit tests, execute the `./scripts/test.sh` script and you should see the following +output. + + Chrome: Runner reset. + .. + Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (3.00 ms) + Chrome 11.0.696.57 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms) + + +Now that you have learned how easy it is to use angular services (thanks to angular's +implementation of dependency injection), go to Step 6, where you will add some thumbnail images of +phones and some links. + + +<table id="tutorial_nav"> +<tr> + <td id="previous_step">{@link tutorial.step_04 Previous}</td> + <td id="step_result">{@link http://angular.github.com/angular-phonecat/step-5/app Live Demo +}</td> + <td id="tut_home">{@link tutorial Tutorial Home}</td> + <td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-4...step-5 + Code Diff}</td> + <td id="next_step">{@link tutorial.step_06 Next}</td> +</tr> +</table> diff --git a/docs/content/tutorial/step_06.ngdoc b/docs/content/tutorial/step_06.ngdoc index afe809a6..91862b73 100755 --- a/docs/content/tutorial/step_06.ngdoc +++ b/docs/content/tutorial/step_06.ngdoc @@ -1,113 +1,119 @@ -@workInProgress
-@ngdoc overview
-@name Tutorial: Step 6
-@description
-<table id="tutorial_nav">
-<tr>
-<td id="previous_step">{@link tutorial.step_05 Previous}</td>
-<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-6/app Example}</td>
-<td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-5...step-6 Code
-Diff}</td>
-<td id="next_step">{@link tutorial.step_07 Next}</td>
-</tr>
-</table>
-
-In this step, we add thumbnail images, links, and a little more CSS to our app. For now, our
-links go nowhere. One step at a time; in the next step we'll implement new views that these links
-will open.
-
-__`app/index.html`:__
-<pre>
-...
- <ul class="predicates">
- <li>
- Search: <input type="text" name="query"/>
- </li>
- <li>
- Sort by:
- <select name="orderProp">
- <option value="name">Alphabetical</option>
- <option value="age">Newest</option>
- </select>
- </li>
- </ul>
-
- <ul class="phones">
- <li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)">
- <a href="#/phones/{{phone.id}}">{{phone.name}}</a>
- <a href="#/phones/{{phone.id}}" class="thumb"><img ng:src="{{phone.imageUrl}}"></a>
- <p>{{phone.snippet}}</p>
- </li>
- </ul>
-...
-</pre>
-
-__`app/js/controller.js`__ (Unchanged):
-<pre>
-/* App Controllers */
-
-function PhoneListCtrl($xhr) {
- var self = this;
-
- $xhr('GET', 'phones/phones.json', function(code, response) {
- self.phones = response;
- });
-
- self.orderProp = 'age';
-}
-
-//PhoneListCtrl.$inject = ['$xhr'];
-</pre>
-
-__`app/phones/phones.json`__ (sample snippet):
-<pre>
- [
- {
- "age": 4,
- ...
- "carrier": "T-Mobile",
- "id": "motorola-defy-with-motoblur",
- "imageUrl": "http://google.com/phone/image/small/640001",
- "name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
- "snippet": "Are you ready for everything life throws your way?"
- },
- …
- ]
-</pre>
-
-__`test/e2e/scenarios.js`__:
-<pre>
-...
- it('should render phone specific links', function() {
- input('query').enter('nexus');
- element('.phones li a').click();
- expect(browser().location().hash()).toBe('/phones/nexus-s');
- });
-...
-</pre>
-
-## Discussion:
-
-* Note that we're using {@link guide.expression angular expressions} enclosed in the now-familiar
-{@link angular.markup double-curly brace markup} in the href attribute values. These represent
-attribute bindings, and work the same way as the bindings we saw in previous steps.
-
-* Note also the use of the {@link angular.directive.ng:src ng:src} directive in the `<img>` tag.
-That directive prevents the browser from treating the angular `{{ exppression }}` markup
-literally, as it would do if we tried to use markup in a regular `src` attribute. Use `ng:src` to
-keep the browser from eagerly making an extra http request to an invalid location.
-
-* We expanded our end-to-end test to verify that the app is generating correct links to the phone
-views we will implement in the upcoming steps.
-
-<table id="tutorial_nav">
-<tr>
-<td id="previous_step">{@link tutorial.step_05 Previous}</td>
-<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-6/app Example}</td>
-<td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-5...step-6 Code
-Diff}</td>
-<td id="next_step">{@link tutorial.step_07 Next}</td>
-</tr>
-</table>
+@ngdoc overview +@name Tutorial: Step 6 +@description +<table id="tutorial_nav"> +<tr> +<td id="previous_step">{@link tutorial.step_05 Previous}</td> +<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-6/app Live Demo +}</td> +<td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-5...step-6 Code +Diff}</td> +<td id="next_step">{@link tutorial.step_07 Next}</td> +</tr> +</table> + +In this step, you will add thumbnail images for the phones in the phone list, and links that, for +now, will go nowhere. In subsequent steps you will use the links to display additional information +about the phones in the catalog. + +1. Reset your workspace to Step 6 using: + + git checkout --force step-6 + +or + + ./goto_step.sh 6 + +2. Refresh your browser or check the app out on {@link +http://angular.github.com/angular-phonecat/step-6/app our server}. 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 {@link +https://github.com/angular/angular-phonecat/compare/step-5...step-6 +GitHub}: + + +## Data + +Note that the `phones.json` file contains unique ids and image urls for each of the phones. The +urls point to the `app/img/phones/` directory. + +__`app/phones/phones.json`__ (sample snippet): +<pre> + [ + { + ... + "id": "motorola-defy-with-motoblur", + "imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg", + "name": "Motorola DEFY\u2122 with MOTOBLUR\u2122", + ... + }, + ... + ] +</pre> + + +## Template + +__`app/index.html`:__ +<pre> +... + <ul class="phones"> + <li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)"> + <a href="#/phones/{{phone.id}}">{{phone.name}}</a> + <a href="#/phones/{{phone.id}}" class="thumb"><img ng:src="{{phone.imageUrl}}"></a> + <p>{{phone.snippet}}</p> + </li> + </ul> +... +</pre> + +To dynamically generate links that will in the future lead to phone detail pages, we used the +now-familiar {@link angular.markup double-curly brace markup} in the `href` attribute values. In +step 2, we added the `{{phone.name}}` binding as the element content. In this step the +'{{phone.id}}' binding is used in the element attribute. + +We also added phone images next to each record using an image tag with the {@link +angular.directive.ng:src ng:src} directive. That directive prevents the browser from treating the +angular `{{ exppression }}` markup literally, which it would have done if we had only specified an +attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`). Using `ng:src` +prevents the browser from making an http request to an invalid location. + + +## Test + +__`test/e2e/scenarios.js`__: +<pre> +... + it('should render phone specific links', function() { + input('query').enter('nexus'); + element('.phones li a').click(); + expect(browser().location().hash()).toBe('/phones/nexus-s'); + }); +... +</pre> + +We added a new end-to-end test to verify that the app is generating correct links to the phone +views that we will implement in the upcoming steps. + +You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you +can see them running on {@link +http://angular.github.com/angular-phonecat/step-6/test/e2e/runner.html +angular's server}. + +Now that you have added phone images and links, go to Step 7 to learn about angular layout +templates and how angular makes it easy to create applications that have multiple views. + + +<table id="tutorial_nav"> +<tr> +<td id="previous_step">{@link tutorial.step_05 Previous}</td> +<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-6/app Live Demo +}</td> +<td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-5...step-6 Code +Diff}</td> +<td id="next_step">{@link tutorial.step_07 Next}</td> +</tr> +</table> diff --git a/docs/content/tutorial/step_07.ngdoc b/docs/content/tutorial/step_07.ngdoc index 802130c5..3b8b9b45 100755 --- a/docs/content/tutorial/step_07.ngdoc +++ b/docs/content/tutorial/step_07.ngdoc @@ -1,181 +1,211 @@ -@workInProgress
-@ngdoc overview
-@name Tutorial: Step 7
-@description
-<table id="tutorial_nav">
-<tr>
-<td id="previous_step">{@link tutorial.step_06 Previous}</td>
-<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-7/app Live Demo
-}</td>
-<td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-6...step-7 Code
-Diff}</td>
-<td id="next_step">{@link tutorial.step_08 Next}</td>
-</tr>
-</table>
-
-Our app is slowly growing and becoming more complex. Up until now, the app provided our users with
-just one view (the list of all phones), and all of our template code was located in the
-`index.html` file. The next step in building our app is the addition of a view that will show
-detailed information about each of the devices in our list.
-
-To add the detailed view, we could expand the `index.html` file to contain template code for both
-views, but that would get messy very quickly. Instead, we are going to turn the `index.html`
-template into what we call a "layout template". This is a template that is common for all views in
-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.
-
-Similarly as with templates, angular also allows for controllers and scopes managed by these
-controllers to be nested. We are going to create a "root" controller called `PhoneCatCtrl`, which
-will contain the declaration of routes for the application.
-
-Application routes in angular are declared via the {@link angular.service.$route $route} service.
-This services makes it easy to wire together controllers, View templates, and the current URL
-location in the browser. Using this feature we can implement {@link
-http://en.wikipedia.org/wiki/Deep_linking deep linking}, which lets us utilize the browser's
-History, and Back and Forward browser navigation.
-
-We'll use the $route service to declare that our application consists of two different views: one
-view presents the phone listing, and the other view presents the details for a particular phone.
-Each view will have the template stored in a separate file in the `app/partials/` directory.
-Similarly each view will have a controller associated with it. These will be stored in the
-existing `app/js/controllers.js` file.
-
-The `$route` service is usually used in conjunction with the {@link angular.widget.ng:view
-ng:view} widget. The role of the `ng:view` widget is to include the view template for the current
-route into the layout template, which makes it a perfect fit for our `index.html` template.
-
-For now we are going to get all the routing going, and move the phone listing template into a
-separate file. We'll save the implementation of the phone details View for the next step.
-
-__`app/index.html`:__
-<pre>
-...
-<body ng:controller="PhoneCatCtrl">
-
- <ng:view></ng:view>
-
- <script src="lib/angular/angular.js" ng:autobind></script>
- <script src="js/controllers.js"></script>
-</body>
-</html>
-</pre>
-
-__`app/partials/phone-list.html`:__
-<pre>
-<ul class="predicates">
- <li>
- Search: <input type="text" name="query"/>
- </li>
- <li>
- Sort by:
- <select name="orderProp">
- <option value="name">Alphabetical</option>
- <option value="age">Newest</option>
- </select>
- </li>
-</ul>
-
-<ul class="phones">
- <li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)">
- <a href="#/phones/{{phone.id}}">{{phone.name}}</a>
- <a href="#/phones/{{phone.id}}" class="thumb"><img ng:src="{{phone.imageUrl}}"></a>
- <p>{{phone.snippet}}</p>
- </li>
-</ul>
-</pre>
-
-__`app/partials/phone-list.html`:__
-<pre>
-TBD: detail view for {{params.phoneId}}
-</pre>
-
-__`app/js/controller.js`:__
-<pre>
-/* App Controllers */
-
-function PhoneCatCtrl($route) {
- var self = this;
-
- $route.when('/phones',
- {template: 'partials/phone-list.html', controller: PhoneListCtrl});
- $route.when('/phones/:phoneId',
- {template: 'partials/phone-detail.html', controller: PhoneDetailCtrl});
- $route.otherwise({redirectTo: '/phones'});
-
- $route.onChange(function(){
- self.params = $route.current.params;
- });
-
- $route.parent(this);
-}
-
-//PhoneCatCtrl.$inject = ['$route'];
-
-
-function PhoneListCtrl($xhr) {
- var self = this;
-
- $xhr('GET', 'phones/phones.json', function(code, response) {
- self.phones = response;
- });
-
- self.orderProp = 'age';
-}
-
-//PhoneListCtrl.$inject = ['$xhr'];
-
-
-function PhoneDetailCtrl() {}
-</pre>
-
-## Discussion:
-
-* __The View.__ Our View template in `index.html` has been reduced down to this:
-`<ng:view></ng:view>`. As described above, it is now a "layout template". We added the following
-two new View templates:
-
- * `app/partials/phone-list.html` for the phone list. The phone-list view was formerly our
- main view. We simply moved the code from `index.html` to here.
-
- * `app/partials/phone-detail.html` for the phone details (just a placeholder template for now).
-
-* __The Controller(s).__ We now have a new root controller (`PhoneCatCtrl`) and two
-sub-controllers (`PhoneListCtrl` and `PhoneDetailCtrl`). These inherit the model properties and
-behavior from the root controller.
-
- * __`$route.`__ The root controller's job now is to set up the `$route` configuration:
-
- * When the fragment part of the URL in the browser ends in "/phones", `$route` service
- grabs the `phone-list.html` template, compiles it, and links it with a new scope that is
- controlled by our `PhoneListCtrl` controller.
-
- * When the URL ends in "/phones/:phoneId", `$route` compiles and links the
- `phone-detail.html` template as it did with `phone-list.html`. But note the use of the
- `:phoneId` parameter declaration in the `path` argument of `$route.when()`: `$route`
- services provides all the values for variables defined in this way as
- `$route.current.params` map. In our route, `$route.current.params.phoneId` always holds
- the current contents of the `:phoneId` portion of the URL. We will use the `phoneId`
- parameter when we fetch the phone details in Step 8.
-
- * Any other URL fragment gets redirected to `/phones`.
-
- * __Controller/Scope inheritance.__ In the function passed into `$route`'s `onChange()`
- method, we copied url parameters extracted from the current route to the `params` property in
- the root scope. This property is inherited by child scopes created for our view controllers
- and accessible by these controllers.
-
- * __Tests.__ To automatically verify that everything is wired properly, we write end to end
- tests that navigate to various URLs and verify that the correct view was rendered.
-
-<table id="tutorial_nav">
-<tr>
-<td id="previous_step">{@link tutorial.step_06 Previous}</td>
-<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-7/app Live Demo
-}</td>
-<td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-6...step-7 Code
-Diff}</td>
-<td id="next_step">{@link tutorial.step_08 Next}</td>
-</tr>
-</table>
+@ngdoc overview +@name Tutorial: Step 7 +@description +<table id="tutorial_nav"> +<tr> +<td id="previous_step">{@link tutorial.step_06 Previous}</td> +<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-7/app Live Demo +}</td> +<td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-6...step-7 Code +Diff}</td> +<td id="next_step">{@link tutorial.step_08 Next}</td> +</tr> +</table> + +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. + +1. Reset your workspace to Step 7 using: + + git checkout --force step-7 + +or + + ./goto_step.sh 7 + +2. Refresh your browser, but be sure that there is nothing in the url after app/index.html, or +check the app out on {@link http://angular.github.com/angular-phonecat/step-7/app our server}. +Note that 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 most important changes are listed below. You can see the full diff on {@link +https://github.com/angular/angular-phonecat/compare/step-6...step-7 +GitHub}: + +## What's going on here? + +Our app is slowly growing and becoming more complex. Before Step 7, the app provided our users +with a single view (the list of all phones), and all of the template code was located in the +`index.html` file. The next step in building the app is the addition of a view that will show +detailed information about each of the devices in our list. + +To add the detailed view, we could expand the `index.html` file to contain template code for both +views, but that would get messy very quickly. Instead, we are going to turn the `index.html` +template into what we call a "layout template". This is a template that is common for all views in +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 angular.service.$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 {@link +http://en.wikipedia.org/wiki/Deep_linking deep linking}, which lets us utilize the browser's +history (back and forward navigation) and bookmarks. + + +## Controllers + +__`app/js/controller.js`:__ +<pre> +function PhoneCatCtrl($route) { + var self = this; + + $route.when('/phones', + {template: 'partials/phone-list.html', controller: PhoneListCtrl}); + $route.when('/phones/:phoneId', + {template: 'partials/phone-detail.html', controller: PhoneDetailCtrl}); + $route.otherwise({redirectTo: '/phones'}); + + $route.onChange(function(){ + self.params = $route.current.params; + }); + + $route.parent(this); +} + +//PhoneCatCtrl.$inject = ['$route']; +... +</pre> + +We created a new controller called `PhoneCatCtrl`. We declared its dependency on the `$route` +service and used this service to declare that our application consists of two different views: + +* The phone list view will be shown when the URL hash fragment is `/phone`. To construct this +view, angular will use the `phone-list.html` template and the `PhoneListCtrl` controller. + +* The phone details view will be show when the URL hash fragment matches '/phone/[phoneId]'. To +construct this view, angular will use the `phone-detail.html` template and the `PhoneDetailCtrl` +controller. + +We reused the `PhoneListCtrl` controller for the first view and we added an empty +`PhoneDetailCtrl` controller to the `app/js/controllers.js` file for the second one. +The statement `$route.otherwise({redirectTo: '/phones'});`, triggers a redirection to `/phones` +when none of our routes is matched. + +Thanks to the `$route.parent(this);` statement and `ng:controller="PhoneCatCtrl"` declaration in +the `index.html` template, the `PhoneCatCtrl` controller has a special role in our app. It is the +"root" controller or the parent controller for the other two sub-controllers (`PhoneListCtrl` and +`PhoneDetailCtrl`). The sub-controllers inherit the model properties and behavior from the root +controller. + + +Note the use of the `:phoneId` parameter in the second route declaration (`'/phones/:phoneId'`). +When the current URL matches this route, the `$route` service extracts the phoneId string from the +current URL and provides it to our controller via the `$route.current.params` map. We will use the +`phoneId` parameter in the `phone-details.html` template. + + + +## Template + +The `$route` service is usually used in conjunction with the {@link angular.widget.ng:view +ng:view} widget. The role of the `ng:view` widget is to include the view template for the current +route into the layout template, which makes it a perfect fit for our `index.html` template. + +__`app/index.html`:__ +<pre> +... +<body ng:controller="PhoneCatCtrl"> + + <ng:view></ng:view> + + <script src="lib/angular/angular.js" ng:autobind></script> + <script src="js/controllers.js"></script> +</body> +</html> +</pre> + +Note that we removed most of the code in the `index.html` template and replaced it with a single +line containing the `ng:view` tag. The code that we removed was placed into the `phone-list.html` +template: + +__`app/partials/phone-list.html`:__ +<pre> +<ul class="predicates"> + <li> + Search: <input type="text" name="query"/> + </li> + <li> + Sort by: + <select name="orderProp"> + <option value="name">Alphabetical</option> + <option value="age">Newest</option> + </select> + </li> +</ul> + +<ul class="phones"> + <li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)"> + <a href="#/phones/{{phone.id}}">{{phone.name}}</a> + <a href="#/phones/{{phone.id}}" class="thumb"><img ng:src="{{phone.imageUrl}}"></a> + <p>{{phone.snippet}}</p> + </li> +</ul> +</pre> + +We also added a placeholder template for the phone details view: + +__`app/partials/phone-list.html`:__ +<pre> +TBD: detail view for {{params.phoneId}} +</pre> + + +## Test + +To automatically verify that everything is wired properly, we wrote end to end tests that navigate +to various URLs and verify that the correct view was rendered. + +<pre> +... + it('should redirect index.html to index.html#/phones', function() { + browser().navigateTo('../../app/index.html'); + expect(browser().location().hash()).toBe('/phones'); + }); +... + + describe('Phone detail view', function() { + + beforeEach(function() { + browser().navigateTo('../../app/index.html#/phones/nexus-s'); + }); + + + it('should display placeholder page with phoneId', function() { + expect(binding('params.phoneId')).toBe('nexus-s'); + }); + }); +</pre> + + +You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you +can see them running on {@link +http://angular.github.com/angular-phonecat/step-7/test/e2e/runner.html +angular's server}. + + +With the routing set up and the phone list view implemented, we're ready to go to Step 8 to +implement the phone details view. + +<table id="tutorial_nav"> +<tr> +<td id="previous_step">{@link tutorial.step_06 Previous}</td> +<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-7/app Live Demo +}</td> +<td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-6...step-7 Code +Diff}</td> +<td id="next_step">{@link tutorial.step_08 Next}</td> +</tr> +</table> diff --git a/docs/content/tutorial/step_08.ngdoc b/docs/content/tutorial/step_08.ngdoc index 65ce6883..2d5c0ab9 100755 --- a/docs/content/tutorial/step_08.ngdoc +++ b/docs/content/tutorial/step_08.ngdoc @@ -1,148 +1,200 @@ -@workInProgress
-@ngdoc overview
-@name Tutorial: Step 8
-@description
-<table id="tutorial_nav">
-<tr>
-<td id="previous_step">{@link tutorial.step_07 Previous}</td>
-<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-8/app Live Demo
-}</td>
-<td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-7...step-8 Code
-Diff}</td>
-<td id="next_step">{@link tutorial.step_09 Next}</td>
-</tr>
-</table>
-
-In this step, we implement the Phone Details View template. Once again we will use {@link
-angular.services.$xhr $xhr} to fetch our data, and we'll flesh out the `phone-details.html` View
-template.
-
-__`app/partials/phone-details.html`:__
-<pre>
-<img ng:src="{{phone.images[0]}}" class="phone"/>
-
-<h1>{{phone.name}}</h1>
-
-<p>{{phone.description}}</p>
-
-<ul class="phone-thumbs">
- <li ng:repeat="img in 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 phone.availability">{{availability}}</dd>
- </dl>
- </li>
- ...
- </li>
- <span>Additional Features</span>
- <dd>{{phone.additionalFeatures}}</dd>
- </li>
-</ul>
-</pre>
-
-__`app/js/controller.js`:__
-<pre>
-function PhoneCatCtrl($route) (same as Step 7)
-
-function PhoneListCtrl($xhr) (same as Step 7)
-
-function PhoneDetailCtrl($xhr) {
- var self = this;
-
- $xhr('GET', 'phones/' + self.params.phoneId + '.json', function(code, response) {
- self.phone = response;
- });
-}
-
-//PhoneDetailCtrl.$inject = ['$xhr'];
-</pre>
-
-__`app/phones/nexus-s.json`:__ (sample snippet)
-<pre>
-{
- "additionalFeatures": "Contour Display, Near Field Communications (NFC), Three-axis gyroscope,
- Anti-fingerprint display coating, Internet Calling support (VoIP/SIP)",
- "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"
- }
-}
-</pre>
-
-__`test/unit/controllerSpec.js`:__
-<pre>
-...
- it('should fetch phone detail', function(){
- scope.params = {phoneId:'xyz'};
- $browser.xhr.expectGET('phones/xyz.json').respond({name:'phone xyz'});
- ctrl = scope.$new(PhoneDetailCtrl);
-
- expect(ctrl.phone).toBeUndefined();
- $browser.xhr.flush();
-
- expect(ctrl.phone).toEqual({name:'phone xyz'});
- });
-...
-</pre>
-
-__`test/e2e/scenarios.js`:__
-<pre>
-...
- describe('Phone detail view', function() {
-
- beforeEach(function() {
- browser().navigateTo('../../app/index.html#/phones/nexus-s');
- });
-
-
- it('should display nexus-s page', function() {
- expect(binding('phone.name')).toBe('Nexus S');
- });
- });
-...
-</pre>
-
-## Discussion:
-
-* Phone Details View Template. There is nothing fancy or new here, just note where we use the
-angular `{{ expression }}` markup and directives to project phone data from our model into the
-view.
-
-* Note how we used the `$route` `params` object from the scope managed by the root controller
-(`PhoneCatCtrl`), to construct the path for the phone details xhr request. The rest of this step
-is simply applying the previously learned concepts and angular APIs to create a large template
-that displays a lot of data about a phone.
-
-* Tests. We updated the existing end to end test and wrote a new unit test that is similar in
-spirit to the one we wrote for the `PhoneListCtrl` controller.
-
-<table id="tutorial_nav">
-<tr>
-<td id="previous_step">{@link tutorial.step_07 Previous}</td>
-<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-8/app Live Demo
-}</td>
-<td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-7...step-8 Code
-Diff}</td>
-<td id="next_step">{@link tutorial.step_09 Next}</td>
-</tr>
-</table>
+@ngdoc overview +@name Tutorial: Step 8 +@description +<table id="tutorial_nav"> +<tr> +<td id="previous_step">{@link tutorial.step_07 Previous}</td> +<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-8/app Live Demo +}</td> +<td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-7...step-8 Code +Diff}</td> +<td id="next_step">{@link tutorial.step_09 Next}</td> +</tr> +</table> + +In this step, you will implement the phone details view, which is displayed when a user clicks on +a phone in the phone list. + +1. Reset your workspace to Step 8 using: + + git checkout --force step-8 + +or + + ./goto_step.sh 8 + +2. Refresh your browser or check the app out on {@link +http://angular.github.com/angular-phonecat/step-8/app our server}. 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 angular.services.$xhr $xhr} to fetch our +data, and we'll flesh out the `phone-details.html` view template. + +The most important changes are listed below. You can see the full diff on {@link +https://github.com/angular/angular-phonecat/compare/step-7...step-8 +GitHub}: + +## Data + +In addition to `phones.json`, the `app/phones/` directory also contains one json file for each +phone: + +__`app/phones/nexus-s.json`:__ (sample snippet) +<pre> +{ + "additionalFeatures": "Contour Display, Near Field Communications (NFC), Three-axis gyroscope, + Anti-fingerprint display coating, Internet Calling support (VoIP/SIP)", + "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" + } +} +</pre> + + +Each of these files describes various properties of the phone using the same data structure. We'll +show this data in the phone detail view. + + +## Controller + +We'll expand the `PhoneDetailCtrl` by using the `$xhr` service to fetch the json files. This works +the same way as the phone list controller. + +__`app/js/controller.js`:__ +<pre> +function PhoneDetailCtrl($xhr) { + var self = this; + + $xhr('GET', 'phones/' + self.params.phoneId + '.json', function(code, response) { + self.phone = response; + }); +} + +//PhoneDetailCtrl.$inject = ['$xhr']; +</pre> + + + + +## Template + +The TBD placeholder line has been replaced with lists and bindings that comprise the phone +details. Note where we use the angular `{{ expression }}` markup and `ng:repeater`s to project +phone data from our model into the view. + + +__`app/partials/phone-details.html`:__ +<pre> +<img ng:src="{{phone.images[0]}}" class="phone"/> + +<h1>{{phone.name}}</h1> + +<p>{{phone.description}}</p> + +<ul class="phone-thumbs"> + <li ng:repeat="img in 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 phone.availability">{{availability}}</dd> + </dl> + </li> + ... + </li> + <span>Additional Features</span> + <dd>{{phone.additionalFeatures}}</dd> + </li> +</ul> +</pre> + + +## Test + +We wrote a new unit test that is similar to the one we wrote for the `PhoneListCtrl` controller in +Step 5. + +__`test/unit/controllerSpec.js`:__ +<pre> +... + it('should fetch phone detail', function(){ + scope.params = {phoneId:'xyz'}; + $browser.xhr.expectGET('phones/xyz.json').respond({name:'phone xyz'}); + ctrl = scope.$new(PhoneDetailCtrl); + + expect(ctrl.phone).toBeUndefined(); + $browser.xhr.flush(); + + expect(ctrl.phone).toEqual({name:'phone xyz'}); + }); +... +</pre> + +To run the unit tests, execute the `./scripts/test.sh` script and you should see the following +output. + + Chrome: Runner reset. + ... + Total 3 tests (Passed: 3; Fails: 0; Errors: 0) (5.00 ms) + Chrome 11.0.696.57 Mac OS: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (5.00 ms) + + +We also added a new end-to-end test that navigates to the Nexus S detail page and verifies that +the heading on the page is "Nexus S". + +__`test/e2e/scenarios.js`:__ +<pre> +... + describe('Phone detail view', function() { + + beforeEach(function() { + browser().navigateTo('../../app/index.html#/phones/nexus-s'); + }); + + + it('should display nexus-s page', function() { + expect(binding('phone.name')).toBe('Nexus S'); + }); + }); +... +</pre> + + +You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you +can see them running on {@link +http://angular.github.com/angular-phonecat/step-8/test/e2e/runner.html +angular's server}. + +Now the phone details view is in place, proceed to Step 9 to learn how to write your own custom +display filter. + +<table id="tutorial_nav"> +<tr> +<td id="previous_step">{@link tutorial.step_07 Previous}</td> +<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-8/app Live Demo +}</td> +<td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-7...step-8 Code +Diff}</td> +<td id="next_step">{@link tutorial.step_09 Next}</td> +</tr> +</table> diff --git a/docs/content/tutorial/step_09.ngdoc b/docs/content/tutorial/step_09.ngdoc index 2d6ed925..1009217d 100755 --- a/docs/content/tutorial/step_09.ngdoc +++ b/docs/content/tutorial/step_09.ngdoc @@ -1,108 +1,127 @@ -@workInProgress
-@ngdoc overview
-@name Tutorial: Step 9
-@description
-<table id="tutorial_nav">
-<tr>
-<td id="previous_step">{@link tutorial.step_08 Previous}</td>
-<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-9/app Live Demo
-}</td>
-<td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-8...step-9 Code
-Diff}</td>
-<td id="next_step">{@link tutorial.step_10 Next}</td>
-</tr>
-</table>
-
-In this step, we have determined that the built-in angular display filters ({@link
-angular.filter.number number}, {@link angular.filter.currency currency}, {@link
-angular.filter.date date}, etc.) don't handle what we want to do, so we get to create our own
-custom {@link angular.filter filter}.
-
-In the previous step, the details page displayed either "true" or "false" to indicate whether
-certain phone features were present or not. Our custom "checkmark" filter replaces those text
-strings with glyphs: ✓ for "true", and ✘ for "false".
-
-Our filter code lives in `app/js/filters.js`:
-
-__`app/index.html`:__
-<pre>
-...
- <script src="lib/angular/angular.js" ng:autobind></script>
- <script src="js/controllers.js"></script>
- <script src="app/js/filters.js"></script>
-...
-</pre>
-
-In the phone details template, we employ our filter for angular expressions whose values are
-"true" or "false"; `{{ [phone_feature] | checkmark }}`:
-
-__`app/partials/phone-detail.html`:__
-<pre>
-<img ng:src="{{phone.images[0].large}}" class="phone"/>
-<h1>{{phone.name}}</h1>
-<p>{{phone.description}}</p>
-...
-<ul class="specs">
- ...
- <li>
- <span>Connectivity</span>
- <dl>
- <dt>Network Support</dt>
- <dd>{{phone.connectivity.cell}}</dd>
- <dt>WiFi</dt>
- <dd>{{phone.connectivity.wifi}}</dd>
- <dt>Bluetooth</dt>
- <dd>{{phone.connectivity.bluetooth}}</dd>
- <dt>Infrared</dt>
- <dd>{{phone.connectivity.infrared | checkmark}}</dd>
- <dt>GPS</dt>
- <dd>{{phone.connectivity.gps | checkmark}}</dd>
- </dl>
- </li>
-...
-</ul>
-</pre>
-
-__`app/js/filters.js`:__ (New)
-<pre>
-angular.filter('checkmark', function(input) {
- return input ? '\u2713' : '\u2718';
-});
-</pre>
-
-__`test/unit/filtersSpec.js`:__ (New)
-<pre>
-describe('checkmark filter', function() {
-
- it('should convert boolean values to unicode checkmark or cross', function() {
- expect(angular.filter.checkmark(true)).toBe('\u2713');
- expect(angular.filter.checkmark(false)).toBe('\u2718');
- });
-})
-</pre>
-
-## Discussion:
-
-* This example shows how easy it is to roll your own filters for displaying data. As explained in
-the "Writing your own Filters" section of the {@link angular.filter angular.filter} page, you
-simply register your custom filter function on to the `angular.filter` function.
-
-* In this example, our filter name is "checkmark"; our input is either "true" or "false", and we
-return one of two unicode characters we have chosen to represent true or false (`\u2713` and
-`\u2718`).
-
-* We created a new unit test to verify that our custom filter converts boolean values to unicode
-characters.
-
-<table id="tutorial_nav">
-<tr>
-<td id="previous_step">{@link tutorial.step_08 Previous}</td>
-<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-9/app Live Demo
-}</td>
-<td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-8...step-9 Code
-Diff}</td>
-<td id="next_step">{@link tutorial.step_10 Next}</td>
-</tr>
-</table>
+@ngdoc overview +@name Tutorial: Step 9 +@description +<table id="tutorial_nav"> +<tr> +<td id="previous_step">{@link tutorial.step_08 Previous}</td> +<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-9/app Live Demo +}</td> +<td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-8...step-9 Code +Diff}</td> +<td id="next_step">{@link tutorial.step_10 Next}</td> +</tr> +</table> + +In this step you will learn how to create your own custom display filter. + +1. Reset your workspace to Step 9 using: + + git checkout --force step-9 + +or + + ./goto_step.sh 9 + +2. Refresh your browser or check the app out on {@link +http://angular.github.com/angular-phonecat/step-9/app our server}. Navigate to one of the detail +pages. + +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 {@link +https://github.com/angular/angular-phonecat/compare/step-8...step-9 +GitHub}: + + +## Custom Filter + +In order to create a new filter, simply register your custom filter function with the `{@link +angular.filter angular.filter}` API. + +__`app/js/filters.js`:__ +<pre> +angular.filter('checkmark', function(input) { + return input ? '\u2713' : '\u2718'; +}); +</pre> + +The name of our filter is "checkmark". The `input` evaluates to either "true" or "false", and we +return one of two unicode characters we have chosen to represent true or false (`\u2713` and +`\u2718`). + + +## Template + +Since the filter code lives in the `app/js/filters.js` file, we need to include this file in our +layout template. + +__`app/index.html`:__ +<pre> +... + <script src="js/controllers.js"></script> + <script src="js/filters.js"></script> +... +</pre> + +The syntax for using filters in angular templates is as follows: + + {{ expression | filter }} + +Let's employ the filter in the phone details template: + + + +__`app/partials/phone-detail.html`:__ +<pre> +... + <dl> + <dt>Infrared</dt> + <dd>{{phone.connectivity.infrared | checkmark}}</dd> + <dt>GPS</dt> + <dd>{{phone.connectivity.gps | checkmark}}</dd> + </dl> +... +</pre> + + +## Test + +Filters, like any other component, should be tested and these tests are very easy to write. + +__`test/unit/filtersSpec.js`:__ +<pre> +describe('checkmark filter', function() { + + it('should convert boolean values to unicode checkmark or cross', function() { + expect(angular.filter.checkmark(true)).toBe('\u2713'); + expect(angular.filter.checkmark(false)).toBe('\u2718'); + }); +}) +</pre> + +To run the unit tests, execute the `./scripts/test.sh` script and you should see the following +output. + + Chrome: Runner reset. + .... + Total 4 tests (Passed: 4; Fails: 0; Errors: 0) (3.00 ms) + Chrome 11.0.696.57 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms) + + +Now that you have learned how to write and test a custom filter, go to Step 10 to learn how we can +use angular to enhance the phone details page further. + +<table id="tutorial_nav"> +<tr> +<td id="previous_step">{@link tutorial.step_08 Previous}</td> +<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-9/app Live Demo +}</td> +<td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-8...step-9 Code +Diff}</td> +<td id="next_step">{@link tutorial.step_10 Next}</td> +</tr> +</table> diff --git a/docs/content/tutorial/step_10.ngdoc b/docs/content/tutorial/step_10.ngdoc index 130b4023..1614c414 100644 --- a/docs/content/tutorial/step_10.ngdoc +++ b/docs/content/tutorial/step_10.ngdoc @@ -1,4 +1,3 @@ -@workInProgress @ngdoc overview @name Tutorial: Step 10 @description @@ -14,25 +13,29 @@ Code Diff}</td> </tr> </table> +In this step, you will add a clickable phone image swapper to the phone details page. + +1. Reset your workspace to Step 10 using: + + git checkout --force step-10 + +or + + ./goto_step.sh 10 + +2. Refresh your browser or check the app out on {@link +http://angular.github.com/angular-phonecat/step-10/app our server}. + 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 how we can do this with angular. -__`app/partials/phone-detail.html`:__ -<pre> -<img ng:src="{{mainImageUrl}}" class="phone"/> +The most important changes are listed below. You can see the full diff on {@link +https://github.com/angular/angular-phonecat/compare/step-9...step-10 +GitHub}: -<h1>{{phone.name}}</h1> -<p>{{phone.description}}</p> - -<ul class="phone-thumbs"> - <li ng:repeat="img in phone.images"> - <img ng:src="{{img}}" ng:click="setImage(img)"> - </li> -</ul> -... -</pre> +## Controller __`app/js/controllers.js`:__ <pre> @@ -53,9 +56,43 @@ function PhoneDetailCtrl($xhr) { //PhoneDetailCtrl.$inject = ['$xhr']; </pre> +In the `PhoneDetailCtrl` controller, the statement `self.mainImageUrl = response.images[0];` +creates the `mainImageUrl` model property and set its default value to the first phone image url. + +We also created a `setImage` controller method to change the value of `mainImageUrl`. + + +## Template + +__`app/partials/phone-detail.html`:__ +<pre> +<img ng:src="{{mainImageUrl}}" class="phone"/> + +... + +<ul class="phone-thumbs"> + <li ng:repeat="img in phone.images"> + <img ng:src="{{img}}" ng:click="setImage(img)"> + </li> +</ul> +... +</pre> + +We bound the `ng:src` attribute of the large image to the `mainImageUrl` property. + +We also registered an `{@link angular.directive.ng:click ng:click}` handler with thumbnail images. +When a user clicks on one of the thumbnail images, the handler will use the `setImage` controller +method to change the value of the `mainImageUrl` property to the url of the thumbnail image. + + +## Test + +To verify this new feature, we added two end-to-end tests. One verifies that the main image is set +to the first phone image by default. The second test clicks on several thumbnail images and +verifies that the main image changed appropriately. + __`test/e2e/scenarios.js`:__ <pre> -/* jasmine-like end2end tests go here */ ... describe('Phone detail view', function() { @@ -64,10 +101,6 @@ __`test/e2e/scenarios.js`:__ }); - it('should display nexus-s page', function() { - expect(binding('phone.name')).toBe('Nexus S'); - }); - it('should display the first phone image as the main phone image', function() { expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg'); }); @@ -84,18 +117,14 @@ __`test/e2e/scenarios.js`:__ }); </pre> -## Discussion: - -Adding the phone image swapping feature is fairly straightforward: +You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you +can see them running on {@link +http://angular.github.com/angular-phonecat/step-8/test/e2e/runner.html +angular's server}. -* We defined the `mainImageUrl` model property in the details controller (`PhoneDetailCtrl`) and -set the default value of `mainImageUrl` to the first image in the array of images. -* We created a `setImage` controller method to change `mainImageUrl` to the image clicked on by -the user. -* We registered an `{@link angular.directive.ng:click ng:click}` handler for thumb images to use -the `setImage` controller method. -* We expanded the end-to-end test to verify that our new feature is swapping images correctly. +With the phone image swapper in place, we're ready for Step 11 (the last step!) to learn an even +better way to fetch data. <table id="tutorial_nav"> <tr> diff --git a/docs/content/tutorial/step_11.ngdoc b/docs/content/tutorial/step_11.ngdoc index e383f406..5dc45d90 100644 --- a/docs/content/tutorial/step_11.ngdoc +++ b/docs/content/tutorial/step_11.ngdoc @@ -1,178 +1,248 @@ -@workInProgress
-@ngdoc overview
-@name Tutorial: Step 11
-@description
-<table id="tutorial_nav">
-<tr>
-<td id="previous_step">{@link tutorial.step_10 Previous}</td>
-<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-11/app Live Demo
-}</td>
-<td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-10...step-11
-Code Diff}</td>
-<td id="next_step">Next</td>
-</tr>
-</table>
-
-And so we arrive at the last step of this tutorial. Here we define a custom service that
-represents a {@link http://en.wikipedia.org/wiki/Representational_State_Transfer RESTful} client.
-Using this client we can make xhr requests for data in an easier way, without having to deal with
-the lower-level {@link angular.service.$xhr $xhr} APIs, HTTP methods and URLs.
-
-__`app/index.html`.__
-<pre>
-...
- <script src="js/services.js"></script>
-...
-</pre>
-
-
-__`app/js/services.js`.__ (New)
-<pre>
- angular.service('Phone', function($resource){
- return $resource('phones/:phoneId.json', {}, {
- query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
- });
- });
-</pre>
-
-__`app/js/controllers.js`.__
-<pre>
-...
-
-function PhoneListCtrl(Phone_) {
- this.orderProp = 'age';
- this.phones = Phone_.query();
-}
-//PhoneListCtrl.$inject = ['Phone'];
-
-
-function PhoneDetailCtrl(Phone_) {
- this.phone = Phone_.get({phoneId:this.params.phoneId});
-}
-//PhoneDetailCtrl.$inject = ['Phone'];
-</pre>
-
-__`test/unit/controllersSpec.js`:__
-<pre>
-/* jasmine specs for controllers go here */
-describe('PhoneCat controllers', function() {
-
- beforeEach(function(){
- this.addMatchers({
- toEqualData: function(expected) {
- return angular.equals(this.actual, expected);
- }
- });
- });
-
- describe('PhoneListCtrl', function(){
- var scope, $browser, ctrl;
-
- beforeEach(function() {
- scope = angular.scope();
- $browser = scope.$service('$browser');
-
- $browser.xhr.expectGET('phones/phones.json').respond([{name: 'Nexus S'},
- {name: 'Motorola DROID'}]);
- ctrl = scope.$new(PhoneListCtrl);
- });
-
- it('should create "phones" model with 2 phones fetched from xhr', function() {
- expect(ctrl.phones).toEqual([]);
- $browser.xhr.flush();
-
- expect(ctrl.phones).toEqualData([{name: 'Nexus S'},
- {name: 'Motorola DROID'}]);
- });
-
- it('should set the default value of orderProp model', function() {
- expect(ctrl.orderProp).toBe('age');
- });
- });
-
-
- describe('PhoneDetailCtrl', function(){
- var scope, $browser, ctrl;
-
- beforeEach(function() {
- scope = angular.scope();
- $browser = scope.$service('$browser');
- });
-
- beforeEach(function() {
- scope = angular.scope();
- $browser = scope.$service('$browser');
- });
-
- it('should fetch phone detail', function(){
- scope.params = {phoneId:'xyz'};
- $browser.xhr.expectGET('phones/xyz.json').respond({name:'phone xyz'});
- ctrl = scope.$new(PhoneDetailCtrl);
-
- expect(ctrl.phone).toEqualData({});
- $browser.xhr.flush();
-
- expect(ctrl.phone).toEqualData({name:'phone xyz'});
- });
- });
-});
-</pre>
-
-
-## Discussion:
-
-* We simplified our sub-controllers (`PhoneListCtrl` and `PhoneDetailCtrl`) by factoring out the
-lower-level `$xhr` service, replacing it with a new service called `Phone`. Angular's {@link
-angular.service.$resource `$resource`} service is easier to use than `$xhr` for interacting with
-data sources exposed as RESTful resources. It is also easier now to understand what the code in
-our controllers is doing.
-
- An important thing to notice in our controller code is that we don't pass any callback
- functions when invoking methods of our Phone services. It looks as if the result were returned
- synchronously. That is not the case at all. What is returned synchronously is a "future" — an
- object, which will be filled with data when the xhr response returns. Because of the
- data-binding in angular, we can use this future and bind it to our template. Then, when the
- data arrives, the view will automatically update. See? Angular tries hard to make simple
- stuff simple.
-
-* Once again we make use of `$route's` params, this time to construct the URL passed as a
-parameter to `$resource` in our `services.js` script.
-
-* Last, but certainly not least, we expanded and modified our unit test to verify that our new
-service is returning data as we expect it to.
-
- In our assertions we use a newly-defined `toEqualData` {@link
- http://pivotal.github.com/jasmine/jsdoc/symbols/jasmine.Matchers.html Jasmine matcher}, which
- compares only object properties and ignores methods. This is necessary, because the `$resource`
- client will augment the response object with handy methods for updating and deleting the
- resource (we don't use these in our tutorial though).
-
-There you have it! We have created a web app in a relatively short amount of time.
-
-## Closing Notes:
-
-* For more details and examples of the angular concepts we touched on in this tutorial, see the
-{@link guide Developer Guide}.
-
-* For several more examples of sample code, see the {@link cookbook Cookbook}.
-
-* When you are ready to start developing a project using angular, be sure to begin with the {@link
-https://github.com/angular/angular-seed angular seed app}.
-
-* We hope this tutorial was useful to you, and that you learned enough about angular to make you
-want to learn more. Of course, we especially hope you are inspired to go out and develop angular
-web apps of your own, and perhaps you might even be interested in {@link contribute contributing}
-to angular.
-
-<table id="tutorial_nav">
-<tr>
-<td id="previous_step">{@link tutorial.step_10 Previous}</td>
-<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-11/app Live Demo
-}</td>
-<td id="tut_home">{@link tutorial Tutorial Home}</td>
-<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-10...step-11
-Code Diff}</td>
-<td id="next_step">Next</td>
-</tr>
-</table>
+@ngdoc overview +@name Tutorial: Step 11 +@description +<table id="tutorial_nav"> +<tr> +<td id="previous_step">{@link tutorial.step_10 Previous}</td> +<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-11/app Live Demo +}</td> +<td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-10...step-11 +Code Diff}</td> +<td id="next_step">Next</td> +</tr> +</table> + +In this step, you will improve the way our app fetches data. + +1. Reset your workspace to Step 11 using: + + git checkout --force step-11 + +or + + ./goto_step.sh 11 + +2. Refresh your browser or check the app out on {@link +http://angular.github.com/angular-phonecat/step-11/app our server}. + + +The last improvement we will make to our app is to define a custom service that represents a +{@link http://en.wikipedia.org/wiki/Representational_State_Transfer RESTful} client. Using this +client we can make xhr requests for data in an easier way, without having to deal with the +lower-level {@link angular.service.$xhr $xhr} API, HTTP methods and URLs. + +The most important changes are listed below. You can see the full diff on {@link +https://github.com/angular/angular-phonecat/compare/step-10...step-11 +GitHub}: + + +## Template + +The custom service is defined in `app/js/services.js` so we need to include this file in our +layout template: + +__`app/index.html`.__ +<pre> +... + <script src="js/services.js"></script> +... +</pre> + +## Service + +__`app/js/services.js`.__ +<pre> + angular.service('Phone', function($resource){ + return $resource('phones/:phoneId.json', {}, { + query: {method:'GET', params:{phoneId:'phones'}, isArray:true} + }); + }); +</pre> + +We used the {@link angular.service} API to register a custom service. We passed in the name of the +service - 'Phone' - and a 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. + +The `{@link angular.service.$resource $resource}` service makes it easy to create a {@link +http://en.wikipedia.org/wiki/Representational_State_Transfer RESTful} client with just a few lines +of code. This client can then be used in our application, instead of the lower-level `$xhr` +service. + + +## Controller + +We simplified our sub-controllers (`PhoneListCtrl` and `PhoneDetailCtrl`) by factoring out the +lower-level `$xhr` service, replacing it with a new service called `Phone`. Angular's {@link +angular.service.$resource `$resource`} service is easier to use than `$xhr` for interacting with +data sources exposed as RESTful resources. It is also easier now to understand what the code in +our controllers is doing. + +__`app/js/controllers.js`.__ +<pre> +... + +function PhoneListCtrl(Phone_) { + this.orderProp = 'age'; + this.phones = Phone_.query(); +} +//PhoneListCtrl.$inject = ['Phone']; + + +function PhoneDetailCtrl(Phone_) { + var self = this; + + self.phone = Phone_.get({phoneId: self.params.phoneId}, function(phone) { + self.mainImageUrl = phone.images[0]; + }); + + ... +} +//PhoneDetailCtrl.$inject = ['Phone']; +</pre> + +Notice how in `PhoneListCtrl` we replaced: + + $xhr('GET', 'phones/phones.json', function(code, response) { + self.phones = response; + }); + +with: + + this.phones = Phone_.query(); + +This is a simple statement that we want to query for all phones. + +An important thing to notice in the code above is that we don't pass any callback functions when +invoking methods of our Phone service. Although it looks as if the result were returned +synchronously, that is not the case at all. What is returned synchronously is a "future" — an +object, which will be filled with data when the xhr response returns. Because of the data-binding +in angular, we can use this future and bind it to our template. Then, when the data arrives, the +view will automatically update. + +Sometimes, relying on the future object and data-binding alone is not sufficient to do everything +we require, so in these cases, we can add a callback to process the server response. The +`PhoneDetailCtrl` controller illustrates this by setting the `mainImageUrl` in a callback. + + + + +## Test + +We have modified our unit tests to verify that our new service is issuing HTTP requests and +processing them as expected. The tests also check that our controllers are interacting with the +service correctly. + +The `$resource` client 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` {@link http://pivotal.github.com/jasmine/jsdoc/symbols/jasmine.Matchers.html Jasmine +matcher}. When the `toEqualData` matcher compares two objects, it takes only object properties +into account and ignores methods. + + +__`test/unit/controllersSpec.js`:__ +<pre> +describe('PhoneCat controllers', function() { + + beforeEach(function(){ + this.addMatchers({ + toEqualData: function(expected) { + return angular.equals(this.actual, expected); + } + }); + }); + + describe('PhoneListCtrl', function(){ + var scope, $browser, ctrl; + + beforeEach(function() { + scope = angular.scope(); + $browser = scope.$service('$browser'); + + $browser.xhr.expectGET('phones/phones.json').respond([{name: 'Nexus S'}, + {name: 'Motorola DROID'}]); + ctrl = scope.$new(PhoneListCtrl); + }); + + it('should create "phones" model with 2 phones fetched from xhr', function() { + expect(ctrl.phones).toEqual([]); + $browser.xhr.flush(); + + expect(ctrl.phones).toEqualData([{name: 'Nexus S'}, + {name: 'Motorola DROID'}]); + }); + + it('should set the default value of orderProp model', function() { + expect(ctrl.orderProp).toBe('age'); + }); + }); + + + describe('PhoneDetailCtrl', function(){ + var scope, $browser, ctrl; + + beforeEach(function() { + scope = angular.scope(); + $browser = scope.$service('$browser'); + }); + + beforeEach(function() { + scope = angular.scope(); + $browser = scope.$service('$browser'); + }); + + it('should fetch phone detail', function(){ + scope.params = {phoneId:'xyz'}; + $browser.xhr.expectGET('phones/xyz.json').respond({name:'phone xyz'}); + ctrl = scope.$new(PhoneDetailCtrl); + + expect(ctrl.phone).toEqualData({}); + $browser.xhr.flush(); + + expect(ctrl.phone).toEqualData({name:'phone xyz'}); + }); + }); +}); +</pre> + +To run the unit tests, execute the `./scripts/test.sh` script and you should see the following +output. + + Chrome: Runner reset. + .... + Total 4 tests (Passed: 4; Fails: 0; Errors: 0) (3.00 ms) + Chrome 11.0.696.57 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms) + + +There you have it! We have created a web app in a relatively short amount of time. + +## Closing Notes: + +* For more details and examples of the angular concepts we touched on in this tutorial, see the +{@link guide Developer Guide}. + +* For several more examples of code, see the {@link cookbook Cookbook}. + +* When you are ready to start developing a project using angular, we recommend that you bootstrap +your development with the {@link https://github.com/angular/angular-seed angular seed} project. + +* We hope this tutorial was useful to you and that you learned enough about angular to make you +want to learn more. We especially hope you are inspired to go out and develop angular web apps of +your own, and that you might be interested in {@link contribute contributing} to angular. + +* If you have questions or feedback or just want to say "hi", please post a message at +https://groups.google.com/forum/#!forum/angular. + +<table id="tutorial_nav"> +<tr> +<td id="previous_step">{@link tutorial.step_10 Previous}</td> +<td id="step_result">{@link http://angular.github.com/angular-phonecat/step-11/app Live Demo +}</td> +<td id="tut_home">{@link tutorial Tutorial Home}</td> +<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-10...step-11 +Code Diff}</td> +<td id="next_step">Next</td> +</tr> +</table> |
