Update everything

This commit is contained in:
Colin Frei 2013-04-07 10:12:25 +02:00
parent bf368181a4
commit 72a485d6e8
319 changed files with 67958 additions and 13948 deletions

View file

@ -0,0 +1,112 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><p>A great way to get introduced to AngularJS is to work through this tutorial, which walks you through
the construction of an AngularJS web app. The app you will build is a catalog that displays a list
of Android devices, lets you filter the list to see only devices that interest you, and then view
details for any device.</p>
<p><img class="diagram" src="img/tutorial/catalog_screen.png" width="488" height="413"></p>
<p>Work through the tutorial to see how Angular makes browsers smarter — without the use of extensions
or plug-ins. As you work through the tutorial, you will:</p>
<ul>
<li>See examples of how to use client-side data binding and dependency injection to build dynamic
views of data that change immediately in response to user actions.</li>
<li>See how Angular creates listeners on your data without the need for DOM manipulation.</li>
<li>Learn a better, easier way to test your web apps.</li>
<li>Learn how to use Angular services to make common web tasks, such as getting data into your app,
easier.</li>
</ul>
<p>And all of this works in any browser without modification to the browser!</p>
<p>When you finish the tutorial you will be able to:</p>
<ul>
<li>Create a dynamic application that works in any browser.</li>
<li>Define the differences between Angular and common JavaScript frameworks.</li>
<li>Understand how data binding works in AngularJS.</li>
<li>Use the angular-seed project to quickly boot-strap your own projects.</li>
<li>Create and run tests.</li>
<li>Identify resources for learning more about AngularJS.</li>
</ul>
<p>The tutorial guides you through the entire process of building a simple application, including
writing and running unit and end-to-end tests. Experiments at the end of each step provide
suggestions for you to learn more about AngularJS and the application you are building.</p>
<p>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. If you're looking for a shorter introduction to AngularJS, check out the
<a href="misc/started">Getting Started</a> document.</p>
<h2>Working with the code</h2>
<p>You can follow this tutorial and hack on the code in either the Mac/Linux or the Windows
environment. The tutorial relies on the use of Git versioning system for source code management.
You don't need to know anything about Git to follow the tutorial. Select one of the tabs below
and follow the instructions for setting up your computer.</p>
<div class="tabbable" show="true">
<div class="tab-pane well" id="git-mac" title="Git on Mac/Linux">
<ol>
<li><p>You will need Node.js and Testacular to run unit tests, so please verify that you have
<a href="http://nodejs.org/">Node.js</a> v0.8 or better installed
and that the <code>node</code> executable is on your <code>PATH</code> by running the following
command in a terminal window:</p>
<pre class="prettyprint linenums">node --version</pre>
<p>Additionally install <a href="http://vojtajina.github.com/testacular">Testacular</a> if you
don't have it already:</p>
<pre class="prettyprint linenums">npm install -g testacular</pre>
<li><p>You'll also need Git, which you can get from
<a href="http://git-scm.com/download">the Git site</a>.</p></li>
<li><p>Clone the angular-phonecat repository located at <a
href="https://github.com/angular/angular-phonecat">Github</a> by running the following command:</p>
<pre class="prettyprint linenums">git clone git://github.com/angular/angular-phonecat.git</pre>
<p>This command creates the <code>angular-phonecat</code> directory in your current
directory.</p></li>
<li><p>Change your current directory to <code>angular-phonecat</code>:</p>
<pre class="prettyprint linenums">cd angular-phonecat</pre>
<p>The tutorial instructions assume you are running all commands from the angular-phonecat
directory.</p></li>
<li><p>You will need an http server running on your system. Mac and Linux machines typically
have Apache pre-installed, but If you don't already have one installed, you can use <code>node</code>
to run <code>scripts/web-server.js</code>, a simple bundled http server.</p></li>
</ol>
</div>
<p><div class="tab-pane well" id="git-win" title="Git on Windows">
<ol>
<li><p>You will need Node.js and Testacular to run unit tests, so please verify that you have
<a href="http://nodejs.org/">Node.js</a> v0.8 or better installed
and that the <code>node</code> executable is on your <code>PATH</code> by running the following
command in a terminal window:</p>
<pre class="prettyprint linenums">node --version</pre>
<p>Additionally install <a href="http://vojtajina.github.com/testacular">Testacular</a> if you
don't have it already:</p>
<pre class="prettyprint linenums">npm install -g testacular</pre>
</li>
<li><p>You'll also need Git, which you can get from
<a href="http://git-scm.com/download">the Git site</a>.</p></li>
<li><p>Clone the angular-phonecat repository located at <a
href="https://github.com/angular/angular-phonecat">Github</a> by running the following command:</p>
<pre class="prettyprint linenums">git clone git://github.com/angular/angular-phonecat.git</pre>
<p>This command creates the angular-phonecat directory in your current directory.</p></li>
<li><p>Change your current directory to angular-phonecat.</p>
<pre class="prettyprint linenums">cd angular-phonecat</pre>
<p>The tutorial instructions assume you are running all commands from the angular-phonecat
directory.</p>
<p>You should run all <code>git</code> commands from Git bash.</p>
<p>Other commands like <code>test.bat</code> or <code>e2e-test.bat</code> should be
executed from the Windows command line.</li>
<li><p>You need an http server running on your system, but if you don't already have one
already installed, you can use <code>node</code> to run <code>scripts\web-server.js</code>, a simple
bundled http server.</p></li>
</ol>
</div></p>
<p>The last thing to do is to make sure your computer has a web browser and a good text editor
installed. Now, let's get some cool stuff done!</p>
<p><a href="tutorial/step_00"><span class="btn btn-primary">Get Started!</span></a></p></div>

View file

@ -0,0 +1,206 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><ul doc-tutorial-nav="0"></ul>
<p>You are now ready to build the AngularJS phonecat app. In this step, you will become familiar
with the most important source code files, learn how to start the development servers bundled with
angular-seed, and run the application in the browser.</p>
<div class="tabbable" show="true" ng-model="$cookies.platformPreference">
<div class="tab-pane well" id="git-mac" title="Git on Mac/Linux" value="gitUnix">
<ol>
<li><p>In angular-phonecat directory, run this command:</p>
<pre class="prettyprint linenums">git checkout -f step-0</pre>
<p>This resets your workspace to step 0 of the tutorial app.</p>
<p>You must repeat this for every future step in the tutorial and change the number to
the number of the step you are on. This will cause any changes you made within
your working directory to be lost.</p></li>
<li>To see the app running in a browser, do one of the following:
<ul>
<li><b>For node.js users:</b>
<ol>
<li>In a <i>separate</i> terminal tab or window, run
<code>./scripts/web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
</ol>
</li>
<li><b>For other http servers:</b>
<ol>
<li>Configure the server to serve the files in the <code>angular-phonecat</code>
directory.</li>
<li>Navigate in your browser to
<code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
</ol>
</li>
</ul>
</li>
</ol>
</div>
<div class="tab-pane well" id="git-win" title="Git on Windows" value="gitWin">
<ol>
<li><p>Open Git bash and run this command (in angular-phonecat directory):</p>
<pre class="prettyprint linenums">git checkout -f step-0</pre>
<p>This resets your workspace to step 0 of the tutorial app.</p>
<p>You must repeat this for every future step in the tutorial and change the number to
the number of the step you are on. This will cause any changes you made within
your working directory to be lost.</p></li>
<li>To see the app running in a browser, do one of the following:
<ul>
<li><b>For node.js users:</b>
<ol>
<li>In a <i>separate</i> terminal tab or window, run <code>node
scripts\web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
</ol>
</li>
<li><b>For other http servers:</b>
<ol>
<li>Configure the server to serve the files in the <code>angular-phonecat</code>
directory.</li>
<li>Navigate in your browser to
<code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
</ol>
</li>
</ul>
</li>
</ol>
</div>
</div>
<p>You can now see the page in your browser. It's not very exciting, but that's OK.</p>
<p>The HTML page that displays "Nothing here yet!" was constructed with the HTML code shown below.
The code contains some key Angular elements that we will need going forward.</p>
<p><strong><code>app/index.html</code>:</strong>
<pre class="prettyprint linenums">
&lt;!doctype html&gt;
&lt;html lang="en" ng-app&gt;
&lt;head&gt;
&lt;meta charset="utf-8"&gt;
&lt;title&gt;My HTML File&lt;/title&gt;
&lt;link rel="stylesheet" href="css/app.css"&gt;
&lt;link rel="stylesheet" href="css/bootstrap.css"&gt;
&lt;script src="lib/angular/angular.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;Nothing here {{'yet' + '!'}}&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<h3>What is the code doing?</h3>
<ul>
<li><p><code>ng-app</code> directive:</p>
<pre><code> &lt;html ng-app&gt;
</code></pre>
<p>The <code>ng-app</code> attribute is represents an Angular directive (named <code>ngApp</code>; Angular uses
<code>name-with-dashes</code> for attribute names and <code>camelCase</code> for the corresponding directive name)
used to flag an element which Angular should consider to be the root element of our application.
This gives application developers the freedom to tell Angular if the entire html page or only a
portion of it should be treated as the Angular application.</p></li>
<li><p>AngularJS script tag:</p>
<pre><code> &lt;script src="lib/angular/angular.js"&gt;
</code></pre>
<p>This code downloads the <code>angular.js</code> 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 <a href="api/ng.directive:ngApp"><code>ngApp</code></a> directive. If
Angular finds the directive, it will bootstrap the application with the root of the application DOM
being the element on which the <code>ngApp</code> directive was defined.</p></li>
<li><p>Double-curly binding with an expression:</p>
<pre><code> Nothing here {{'yet' + '!'}}`
</code></pre>
<p>This line demonstrates the core feature of Angular's templating capabilities a binding, denoted
by double-curlies <code>{{ }}</code> as well as a simple expression <code>'yet' + '!'</code> used in this binding.</p>
<p>The binding tells Angular that it should evaluate an expression and insert the result into the
DOM in place of the binding. Rather than a one-time insert, as we'll see in the next steps, a
binding will result in efficient continuous updates whenever the result of the expression
evaluation changes.</p>
<p><a href="guide/expression">Angular expression</a> is a JavaScript-like code snippet that is
evaluated by Angular in the context of the current model scope, rather than within the scope of
the global context (<code>window</code>).</p>
<p>As expected, once this template is processed by Angular, the html page contains the text:
"Nothing here yet!".</p></li>
</ul>
<h3>Bootstrapping AngularJS apps</h3>
<p>Bootstrapping AngularJS apps automatically using the <code>ngApp</code> directive is very easy and suitable
for most cases. In advanced cases, such as when using script loaders, you can use
<a href="guide/bootstrap">imperative / manual way</a> to bootstrap the app.</p>
<p>There are 3 important things that happen during the app bootstrap:</p>
<ol>
<li><p>The <a href="api/AUTO.$injector"><code>injector</code></a> that will be used for dependency injection
within this app is created.</p></li>
<li><p>The injector will then create the <a href="api/ng.$rootScope"><code>root scope</code></a> that will
become the context for the model of our application.</p></li>
<li><p>Angular will then "compile" the DOM starting at the <code>ngApp</code> root element, processing any
directives and bindings found along the way.</p></li>
</ol>
<p>Once an application is bootstrapped, it will then wait for incoming browser events (such as mouse
click, key press or incoming HTTP response) that might change the model. Once such an event occurs,
Angular detects if it caused any model changes and if changes are found, Angular will reflect them
in the view by updating all of the affected bindings.</p>
<p>The structure of our application is currently very simple. The template contains just one directive
and one static binding, and our model is empty. That will soon change!</p>
<p><img class="diagram" src="img/tutorial/tutorial_00.png"></p>
<h3>What are all these files in my working directory?</h3>
<p>Most of the files in your working directory come from the <a href="https://github.com/angular/angular-seed">angular-seed project</a> which is typically used to bootstrap
new Angular projects. The seed project includes the latest Angular libraries, test libraries,
scripts and a simple example app, all pre-configured for developing a typical web app.</p>
<p>For the purposes of this tutorial, we modified the angular-seed with the following changes:</p>
<ul>
<li>Removed the example app</li>
<li>Added phone images to <code>app/img/phones/</code></li>
<li>Added phone data files (JSON) to <code>app/phones/</code></li>
<li>Added <a href="http://twitter.github.com/bootstrap/">Bootstrap</a> files to <code>app/css/</code> and <code>app/img/</code></li>
</ul>
<h2>Experiments</h2>
<ul>
<li><p>Try adding a new expression to the <code>index.html</code> that will do some math:</p>
<pre><code> &lt;p&gt;1 + 2 = {{ 1 + 2 }}&lt;/p&gt;
</code></pre></li>
</ul>
<h2>Summary</h2>
<p>Now let's go to <a href="tutorial/step_01">step 1</a> and add some content to the web app.</p>
<ul doc-tutorial-nav="0"></ul>
<div style="display: none">
Note: During the bootstrap the injector and the root scope will then be associated with the
element on which the `ngApp` directive was declared, so when debugging the app you can retrieve
them from browser console via `angular.element(rootElement).scope()` and
`angular.element(rootElement).injector()`.
</div></div>

View file

@ -0,0 +1,50 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><ul doc-tutorial-nav="1"></ul>
<p>In order to illustrate how Angular enhances standard HTML, you will create a purely <em>static</em> HTML
page and then examine how we can turn this HTML code into a template that Angular will use to
dynamically display the same result with any set of data.</p>
<p>In this step you will add some basic information about two cell phones to an HTML page.</p>
<div doc-tutorial-reset="1">
</div>
<p>The page now contains a list with information about two phones.</p>
<p>The most important changes are listed below. You can see the full diff on <a href="https://github.com/angular/angular-phonecat/compare/step-0...step-1">GitHub</a>:</p>
<p><strong><code>app/index.html</code>:</strong>
<pre class="prettyprint linenums">
&lt;ul&gt;
&lt;li&gt;
&lt;span&gt;Nexus S&lt;/span&gt;
&lt;p&gt;
Fast just got faster with Nexus S.
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span&gt;Motorola XOOM™ with Wi-Fi&lt;/span&gt;
&lt;p&gt;
The Next, Next Generation tablet.
&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</pre>
<h2>Experiments</h2>
<ul>
<li><p>Try adding more static HTML to <code>index.html</code>. For example:</p>
<pre><code> &lt;p&gt;Total number of phones: 2&lt;/p&gt;
</code></pre></li>
</ul>
<h2>Summary</h2>
<p>This addition to your app uses static HTML to display the list. Now, let's go to <a href="tutorial/step_02">step 2</a> to learn how to use AngularJS to dynamically generate the same list.</p>
<ul doc-tutorial-nav="1"></ul></div>

View file

@ -0,0 +1,194 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><ul doc-tutorial-nav="2"></ul>
<p>Now it's time to make the web page dynamic — with AngularJS. We'll also add a test that verifies the
code for the controller we are going to add.</p>
<p>There are many ways to structure the code for an application. For Angular apps, we encourage the
use of <a href="http://en.wikipedia.org/wiki/ModelViewController">the Model-View-Controller (MVC) design pattern</a> to decouple the code and to separate concerns. With that in mind, let's use a
little Angular and JavaScript to add model, view, and controller components to our app.</p>
<div doc-tutorial-reset="2">
</div>
<p>The app now contains a list with three phones.</p>
<p>The most important changes are listed below. You can see the full diff on <a href="https://github.com/angular/angular-phonecat/compare/step-1...step-2">GitHub</a>:</p>
<h3>View and Template</h3>
<p>In Angular, the <strong>view</strong> is a projection of the model through the HTML <strong>template</strong>. This means that
whenever the model changes, Angular refreshes the appropriate binding points, which updates the
view.</p>
<p>The view component is constructed by Angular from this template:</p>
<p><strong><code>app/index.html</code>:</strong>
<pre class="prettyprint linenums">
&lt;html ng-app&gt;
&lt;head&gt;
...
&lt;script src="lib/angular/angular.js"&gt;&lt;/script&gt;
&lt;script src="js/controllers.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body ng-controller="PhoneListCtrl"&gt;
&lt;ul&gt;
&lt;li ng-repeat="phone in phones"&gt;
{{phone.name}}
&lt;p&gt;{{phone.snippet}}&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<p>We replaced the hard-coded phone list with the
<a href="api/ng.directive:ngRepeat"><code>ngRepeat directive</code></a> and two
<a href="guide/expression">Angular expressions</a> enclosed in curly braces:
<code>{{phone.name}}</code> and <code>{{phone.snippet}}</code>:</p>
<ul>
<li><p>The <code>ng-repeat="phone in phones"</code> statement in the <code>&lt;li&gt;</code> tag is an Angular repeater. The
repeater tells Angular to create a <code>&lt;li&gt;</code> element for each phone in the list using the first <code>&lt;li&gt;</code>
tag as the template.</p></li>
<li><p>As we've learned in step 0, the curly braces around <code>phone.name</code> and <code>phone.snippet</code> denote
bindings. As opposed to evaluating constants, these expressions are referring to our application
model, which was set up in our <code>PhoneListCtrl</code> controller.</p>
<p><img class="diagram" src="img/tutorial/tutorial_02.png"></p></li>
</ul>
<h3>Model and Controller</h3>
<p>The data <strong>model</strong> (a simple array of phones in object literal notation) is instantiated within
the <code>PhoneListCtrl</code> <strong>controller</strong>:</p>
<p><strong><code>app/js/controllers.js</code>:</strong>
<pre class="prettyprint linenums">
function PhoneListCtrl($scope) {
$scope.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>
<p>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. We connected the dots between the presentation, data, and logic components
as follows:</p>
<ul>
<li><p><code>PhoneListCtrl</code> — the name of our controller function (located in the JavaScript file
<code>controllers.js</code>), matches the value of the
<a href="api/ng.directive:ngController"><code>ngController</code></a> directive located
on the <code>&lt;body&gt;</code> tag.</p></li>
<li><p>The phone data is then attached to the <em>scope</em> (<code>$scope</code>) that was injected into our controller
function. The controller scope is a prototypical descendant of the root scope that was created
when the application bootstrapped. This controller scope is available to all bindings located within
the <code>&lt;body ng-controller="PhoneListCtrl"&gt;</code> tag.</p>
<p>The concept of a scope in Angular is crucial; a scope can be seen as the glue which allows the
template, model and controller to work together. Angular uses scopes, along with the information
contained in the template, data model, and controller, to keep models and views separate, but in
sync. Any changes made to the model are reflected in the view; any changes that occur in the view
are reflected in the model.</p>
<p>To learn more about Angular scopes, see the <a href="api/ng.$rootScope.Scope"><code>angular scope documentation</code></a>.</p></li>
</ul>
<h3>Tests</h3>
<p>The "Angular way" makes it easy to test code as it is being developed. Take a look at the following
unit test for your newly created controller:</p>
<p><strong><code>test/unit/controllersSpec.js</code>:</strong>
<pre class="prettyprint linenums">
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
it('should create "phones" model with 3 phones', function() {
var scope = {},
ctrl = new PhoneListCtrl(scope);
expect(scope.phones.length).toBe(3);
});
});
});
</pre>
<p>The test verifies that we have three records in the phones array and the example demonstrates how
easy it is to create a unit test for code in Angular. Since testing is such a critical part of
software development, we make it easy to create tests in Angular so that developers are encouraged
to write them.</p>
<p>Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework when
writing tests. Although Angular does not require you to use Jasmine, we wrote all of the tests in
this tutorial in Jasmine. You can learn about Jasmine on the <a href="http://pivotal.github.com/jasmine/">Jasmine home page</a> and on the <a href="https://github.com/pivotal/jasmine/wiki">Jasmine wiki</a>.</p>
<p>The angular-seed project is pre-configured to run all unit tests using <a href="http://vojtajina.github.com/testacular/">Testacular</a>. To run the test, do the following:</p>
<ol>
<li><p>In a <em>separate</em> terminal window or tab, go to the <code>angular-phonecat</code> directory and run
<code>./scripts/test.sh</code> to start the Testacular server.</p></li>
<li><p>Testacular will start a new instance of Chrome browser automatically. Just ignore it and let it run in
the background. Testacular will use this browser for test execution.</p></li>
<li><p>You should see the following or similar output in the terminal:</p>
<pre><code> info: Testacular server started at http://localhost:9876/
info (launcher): Starting browser "Chrome"
info (Chrome 22.0): Connected on socket id tPUm9DXcLHtZTKbAEO-n
Chrome 22.0: Executed 1 of 1 SUCCESS (0.093 secs / 0.004 secs)
</code></pre>
<p>Yay! The test passed! Or not...</p></li>
<li><p>To rerun the tests, just change any of the source or test files. Testacular will notice the change
and will rerun the tests for you. Now isn't that sweet?</p></li>
</ol>
<h2>Experiments</h2>
<ul>
<li><p>Add another binding to <code>index.html</code>. For example:</p>
<pre><code> &lt;p&gt;Total number of phones: {{phones.length}}&lt;/p&gt;
</code></pre></li>
<li><p>Create a new model property in the controller and bind to it from the template. For example:</p>
<pre><code> $scope.hello = "Hello, World!"
</code></pre>
<p>Refresh your browser to make sure it says, "Hello, World!"</p></li>
<li><p>Create a repeater that constructs a simple table:</p>
<pre><code> &lt;table&gt;
&lt;tr&gt;&lt;th&gt;row number&lt;/th&gt;&lt;/tr&gt;
&lt;tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"&gt;&lt;td&gt;{{i}}&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
</code></pre>
<p>Now, make the list 1-based by incrementing <code>i</code> by one in the binding:</p>
<pre><code> &lt;table&gt;
&lt;tr&gt;&lt;th&gt;row number&lt;/th&gt;&lt;/tr&gt;
&lt;tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"&gt;&lt;td&gt;{{i+1}}&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
</code></pre></li>
<li><p>Make the unit test fail by changing the <code>toBe(3)</code> statement to <code>toBe(4)</code>.</p></li>
</ul>
<h2>Summary</h2>
<p>You now have a dynamic app that features separate model, view, and controller components, and you
are testing as you go. Now, let's go to <a href="tutorial/step_03">step 3</a> to learn how to add full text search
to the app.</p>
<ul doc-tutorial-nav="2"></ul></div>

View file

@ -0,0 +1,196 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><ul doc-tutorial-nav="3"></ul>
<p>We did a lot of work in laying a foundation for the app in the last step, so now we'll do something
simple; we will 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.</p>
<div doc-tutorial-reset="3">
</div>
<p>The app now has a search box. Notice that the phone list on the page changes depending on what a
user types into the search box.</p>
<p>The most important differences between Steps 2 and 3 are listed below. You can see the full diff on
<a href="https://github.com/angular/angular-phonecat/compare/step-2...step-3">GitHub</a>:</p>
<h3>Controller</h3>
<p>We made no changes to the controller.</p>
<h3>Template</h3>
<p><strong><code>app/index.html</code>:</strong>
<pre class="prettyprint linenums">
&lt;div class="container-fluid"&gt;
&lt;div class="row-fluid"&gt;
&lt;div class="span2"&gt;
&lt;!--Sidebar content--&gt;
Search: &lt;input ng-model="query"&gt;
&lt;/div&gt;
&lt;div class="span10"&gt;
&lt;!--Body content--&gt;
&lt;ul class="phones"&gt;
&lt;li ng-repeat="phone in phones | filter:query"&gt;
{{phone.name}}
&lt;p&gt;{{phone.snippet}}&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</pre>
<p>We added a standard HTML <code>&lt;input&gt;</code> tag and used Angular's
<a href="api/ng.filter:filter"><code>$filter</code></a> function to process the input for the
<a href="api/ng.directive:ngRepeat"><code>ngRepeat</code></a> directive.</p>
<p>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:</p>
<ul>
<li><p>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.</p>
<p>In this code, the data that a user types into the input box (named <strong><code>query</code></strong>) is immediately
available as a filter input in the list repeater (<code>phone in phones | filter:</code><strong><code>query</code></strong>). 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.</p>
<p><img class="diagram" src="img/tutorial/tutorial_03.png"></p></li>
<li><p>Use of the <code>filter</code> filter: The <a href="api/ng.filter:filter"><code>filter</code></a> function uses the
<code>query</code> value to create a new array that contains only those records that match the <code>query</code>.</p>
<p><code>ngRepeat</code> automatically updates the view in response to the changing number of phones returned
by the <code>filter</code> filter. The process is completely transparent to the developer.</p></li>
</ul>
<h3>Test</h3>
<p>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.</p>
<p>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.</p>
<p><strong><code>test/e2e/scenarios.js</code>:</strong>
<pre class="prettyprint linenums">
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>
<p>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 <a href="guide/dev_guide.e2e-testing">Angular's end-to-end test runner</a>.</p>
<p>To run the end-to-end test, open one of the following in a new browser tab:</p>
<ul>
<li>node.js users: <a href="http://localhost:8000/test/e2e/runner.html">http://localhost:8000/test/e2e/runner.html</a></li>
<li>users with other http servers:
<code>http://localhost:[port-number]/[context-path]/test/e2e/runner.html</code></li>
<li>casual reader: <a href="http://angular.github.com/angular-phonecat/step-3/test/e2e/runner.html">http://angular.github.com/angular-phonecat/step-3/test/e2e/runner.html</a></li>
</ul>
<p>Previously we've seen how Testacular can be used to execute unit tests. Well, it can also run the
end-to-end tests! Use <code>./scripts/e2e-test.sh</code> script for that. End-to-end tests are slow, so unlike
with unit tests, Testacular will exit after the test run and will not automatically rerun the test
suite on every file change. To rerun the test suite, execute the <code>e2e-test.sh</code> script again.</p>
<p>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.</p>
<h2>Experiments</h2>
<ul>
<li><p>Display the current value of the <code>query</code> model by adding a <code>{{query}}</code> binding into the
<code>index.html</code> template, and see how it changes when you type in the input box.</p></li>
<li><p>Let's see how we can get the current value of the <code>query</code> model to appear in the HTML page title.</p>
<p>You might think you could just add the {{query}} to the title tag element as follows:</p>
<pre><code> &lt;title&gt;Google Phone Gallery: {{query}}&lt;/title&gt;
</code></pre>
<p>However, when you reload the page, you won't see the expected result. This is because the "query"
model lives in the scope defined by the body element:</p>
<pre><code> &lt;body ng-controller="PhoneListCtrl"&gt;
</code></pre>
<p>If you want to bind to the query model from the <code>&lt;title&gt;</code> element, you must <strong>move</strong> the
<code>ngController</code> declaration to the HTML element because it is the common parent of both the body
and title elements:</p>
<pre><code> &lt;html ng-app ng-controller="PhoneListCtrl"&gt;
</code></pre>
<p>Be sure to <strong>remove</strong> the <code>ng-controller</code> declaration from the body element.</p>
<p>While using double curlies works fine within the title element, you might have noticed that
for a split second they are actually displayed to the user while the page is loading. A better
solution would be to use the <a href="api/ng.directive:ngBind"><code>ngBind</code></a> or <a href="api/ng.directive:ngBindTemplate"><code>ngBindTemplate</code></a> directives, which are invisible to the user while the page is loading:</p>
<pre><code> &lt;title ng-bind-template="Google Phone Gallery: {{query}}"&gt;Google Phone Gallery&lt;/title&gt;
</code></pre></li>
<li><p>Add the following end-to-end test into the <code>describe</code> block within <code>test/e2e/scenarios.js</code>:</p>
<pre class="prettyprint linenums">
it('should display the current filter value within an element with id "status"',
function() {
expect(element('#status').text()).toMatch(/Current filter: \s*$/);
input('query').enter('nexus');
expect(element('#status').text()).toMatch(/Current filter: nexus\s*$/);
//alternative version of the last assertion that tests just the value of the binding
using('#status').expect(binding('query')).toBe('nexus');
});
</pre>
<p>Refresh the browser tab with the end-to-end test runner to see the test fail. To make the test
pass, edit the <code>index.html</code> template to add a <code>div</code> or <code>p</code> element with <code>id</code> <code>"status"</code> and content
with the <code>query</code> binding, prefixed by "Current filter:". For instance:</p>
<pre><code> &lt;div id="status"&gt;Current filter: {{query}}&lt;/div&gt;
</code></pre></li>
<li><p>Add a <code>pause()</code> statement inside of an end-to-end test and rerun it. You'll see the runner pause;
this gives you the opportunity to explore the state of your application while it is displayed in
the browser. The app is live! You can change the search query to prove it. Notice how useful this
is for troubleshooting end-to-end tests.</p></li>
</ul>
<h2>Summary</h2>
<p>We have now added full text search and included a test to verify that search works! Now let's go on
to <a href="tutorial/step_04">step 4</a> to learn how to add sorting capability to the phone app.</p>
<ul doc-tutorial-nav="3"></ul></div>

View file

@ -0,0 +1,178 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><ul doc-tutorial-nav="4"></ul>
<p>In this step, you will add a feature to let your users control 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.</p>
<div doc-tutorial-reset="4">
</div>
<p>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.</p>
<p>The most important differences between Steps 3 and 4 are listed below. You can see the full diff on
<a href="https://github.com/angular/angular-phonecat/compare/step-3...step-4">GitHub</a>:</p>
<h3>Template</h3>
<p><strong><code>app/index.html</code>:</strong>
<pre class="prettyprint linenums">
Search: &lt;input ng-model="query"&gt;
Sort by:
&lt;select ng-model="orderProp"&gt;
&lt;option value="name"&gt;Alphabetical&lt;/option&gt;
&lt;option value="age"&gt;Newest&lt;/option&gt;
&lt;/select&gt;
&lt;ul class="phones"&gt;
&lt;li ng-repeat="phone in phones | filter:query | orderBy:orderProp"&gt;
{{phone.name}}
&lt;p&gt;{{phone.snippet}}&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</pre>
<p>We made the following changes to the <code>index.html</code> template:</p>
<ul>
<li><p>First, we added a <code>&lt;select&gt;</code> html element named <code>orderProp</code>, so that our users can pick from the
two provided sorting options.</p>
<p><img class="diagram" src="img/tutorial/tutorial_04.png"></p></li>
<li><p>We then chained the <code>filter</code> filter with <a href="api/ng.filter:orderBy"><code><code>orderBy</code></code></a>
filter to further process the input into the repeater. <code>orderBy</code> is a filter that takes an input
array, copies it and reorders the copy which is then returned.</p></li>
</ul>
<p>Angular creates a two way data-binding between the select element and the <code>orderProp</code> model.
<code>orderProp</code> is then used as the input for the <code>orderBy</code> filter.</p>
<p>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!</p>
<h3>Controller</h3>
<p><strong><code>app/js/controllers.js</code>:</strong>
<pre class="prettyprint linenums">
function PhoneListCtrl($scope) {
$scope.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}
];
$scope.orderProp = 'age';
}
</pre>
<ul>
<li><p>We modified the <code>phones</code> model - the array of phones - and added an <code>age</code> property to each phone
record. This property is used to order phones by age.</p></li>
<li><p>We added a line to the controller that sets the default value of <code>orderProp</code> to <code>age</code>. If we had
not set the default value here, the model would stay uninitialized until our user would pick an
option from the drop down menu.</p>
<p>This is a good time to talk about two-way data-binding. Notice that when the app is loaded in the
browser, "Newest" is selected in the drop down menu. This is because we set <code>orderProp</code> to <code>'age'</code>
in the controller. So the binding works in the direction from our model to the UI. Now if you
select "Alphabetically" in the drop down menu, the model will be updated as well and the phones
will be reordered. That is the data-binding doing its job in the opposite direction — from the UI
to the model.</p></li>
</ul>
<h3>Test</h3>
<p>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.</p>
<p><strong><code>test/unit/controllersSpec.js</code>:</strong>
<pre class="prettyprint linenums">
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
var scope, ctrl;
beforeEach(function() {
scope = {},
ctrl = new PhoneListCtrl(scope);
});
it('should create "phones" model with 3 phones', function() {
expect(scope.phones.length).toBe(3);
});
it('should set the default value of orderProp model', function() {
expect(scope.orderProp).toBe('age');
});
});
});
</pre>
<p>The unit test now verifies that the default ordering property is set.</p>
<p>We used Jasmine's API to extract the controller construction into a <code>beforeEach</code> block, which is
shared by all tests in the parent <code>describe</code> block.</p>
<p>You should now see the following output in the Testacular tab:</p>
<pre><code> Chrome 22.0: Executed 2 of 2 SUCCESS (0.021 secs / 0.001 secs)
</code></pre>
<p>Let's turn our attention to the end-to-end test.</p>
<p><strong><code>test/e2e/scenarios.js</code>:</strong>
<pre class="prettyprint linenums">
...
it('should be possible to control phone order via the drop down select box',
function() {
//let's narrow the dataset to make the test assertions shorter
input('query').enter('tablet');
expect(repeater('.phones li', 'Phone List').column('phone.name')).
toEqual(["Motorola XOOM\u2122 with Wi-Fi",
"MOTOROLA XOOM\u2122"]);
select('orderProp').option('Alphabetical');
expect(repeater('.phones li', 'Phone List').column('phone.name')).
toEqual(["MOTOROLA XOOM\u2122",
"Motorola XOOM\u2122 with Wi-Fi"]);
});
...
</pre>
<p>The end-to-end test verifies that the ordering mechanism of the select box is working correctly.</p>
<p>You can now rerun <code>./scripts/e2e-test.sh</code> or refresh the browser tab with the end-to-end test
<code>runner.html</code> to see the tests run, or you can see them running on <a href="http://angular.github.com/angular-phonecat/step-4/test/e2e/runner.html">Angular's server</a>.</p>
<h2>Experiments</h2>
<ul>
<li><p>In the <code>PhoneListCtrl</code> controller, remove the statement that sets the <code>orderProp</code> value and
you'll see that Angular will temporarily add a new "unknown" option to the drop-down list and the
ordering will default to unordered/natural order.</p></li>
<li><p>Add an <code>{{orderProp}}</code> binding into the <code>index.html</code> template to display its current value as
text.</p></li>
</ul>
<h2>Summary</h2>
<p>Now that you have added list sorting and tested the app, go to <a href="tutorial/step_05">step 5</a> to learn
about Angular services and how Angular uses dependency injection.</p>
<ul doc-tutorial-nav="4"></ul></div>

View file

@ -0,0 +1,227 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><ul doc-tutorial-nav="5"></ul>
<p>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 <a href="api/ng">services</a> called <a href="api/ng.$http"><code>$http</code></a>. We will use angular's <a href="guide/di">dependency injection (DI)</a> to provide the service to the <code>PhoneListCtrl</code> controller.</p>
<div doc-tutorial-reset="5">
</div>
<p>You should now see a list of 20 phones.</p>
<p>The most important changes are listed below. You can see the full diff on <a href="https://github.com/angular/angular-phonecat/compare/step-4...step-5">GitHub</a>:</p>
<h3>Data</h3>
<p>The <code>app/phones/phones.json</code> file in your project is a dataset that contains a larger list of phones
stored in the JSON format.</p>
<p>Following is a sample of the file:
<pre class="prettyprint linenums">
[
{
"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>
<h3>Controller</h3>
<p>We'll use angular's <a href="api/ng.$http"><code>$http</code></a> service in our controller to make an HTTP
request to your web server to fetch the data in the <code>app/phones/phones.json</code> file. <code>$http</code> is just
one of several built-in <a href="api/ng">angular services</a> that handle common operations
in web apps. Angular injects these services for you where you need them.</p>
<p>Services are managed by angular's <a href="guide/di">DI subsystem</a>. Dependency injection
helps to make your web apps both well-structured (e.g., separate components for presentation, data,
and control) and loosely coupled (dependencies between components are not resolved by the
components themselves, but by the DI subsystem).</p>
<p><strong><code>app/js/controllers.js:</code></strong>
<pre class="prettyprint linenums">
function PhoneListCtrl($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}
//PhoneListCtrl.$inject = ['$scope', '$http'];
</pre>
<p><code>$http</code> makes an HTTP GET request to our web server, asking for <code>phone/phones.json</code> (the url is
relative to our <code>index.html</code> file). The server responds by providing the data in the json file.
(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.)</p>
<p>The <code>$http</code> service returns a <a href="api/ng.$q"><code>promise object</code></a> with a <code>success</code>
method. We call this method to handle the asynchronous response and assign the phone data to the
scope controlled by this controller, as a model called <code>phones</code>. Notice that angular detected the
json response and parsed it for us!</p>
<p>To use a service in angular, you simply declare the names of the dependencies you need as arguments
to the controller's constructor function, as follows:</p>
<pre><code>function PhoneListCtrl($scope, $http) {...}
</code></pre>
<p>Angular's dependency injector provides services to your 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).</p>
<p>Note that the names of arguments are significant, because the injector uses these to look up the
dependencies.</p>
<p><img class="diagram" src="img/tutorial/xhr_service_final.png"></p>
<h4>'$' Prefix Naming Convention</h4>
<p>You can create your own services, and in fact we will do exactly that in step 11. As a naming
convention, angular's built-in services, Scope methods and a few other angular APIs have a '$'
prefix in front of the name. Don't use a '$' prefix when naming your services and models, in order
to avoid any possible naming collisions.</p>
<h4>A Note on Minification</h4>
<p>Since angular infers the controller's dependencies from the names of arguments to the controller's
constructor function, if you were to <a href="http://en.wikipedia.org/wiki/Minification_(programming)">minify</a> the JavaScript code for <code>PhoneListCtrl</code> controller, all of its function arguments would be
minified as well, and the dependency injector would not be able to identify services correctly.</p>
<p>To overcome issues caused by minification, just assign an array with service identifier strings
into the <code>$inject</code> property of the controller function, just like the last line in the snippet
(commented out) suggests:</p>
<pre><code>PhoneListCtrl.$inject = ['$scope', '$http'];
</code></pre>
<p>There is also one more way to specify this dependency list and avoid minification issues — using the
bracket notation which wraps the function to be injected into an array of strings (representing the
dependency names) followed by the function to be injected:</p>
<pre><code>var PhoneListCtrl = ['$scope', '$http', function($scope, $http) { /* constructor body */ }];
</code></pre>
<p>Both of these methods work with any function that can be injected by Angular, so it's up to your
project's style guide to decide which one you use.</p>
<h3>Test</h3>
<p><strong><code>test/unit/controllersSpec.js</code>:</strong></p>
<p>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 <code>new</code> operator and provide the
constructor with some kind of fake <code>$http</code> implementation. However, the recommended (and easier) way
is to create a controller in the test environment in the same way that angular does it in the
production code behind the scenes, as follows:</p>
<pre class="prettyprint linenums">
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
var scope, ctrl, $httpBackend;
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/phones.json').
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
scope = $rootScope.$new();
ctrl = $controller(PhoneListCtrl, {$scope: scope});
}));
</pre>
<p>Note: Because we loaded Jasmine and <code>angular-mocks.js</code> in our test environment, we got two helper
methods <a href="api/angular.mock.module"><code>module</code></a> and <a href="api/angular.mock.inject"><code>inject</code></a> that we'll
use to access and configure the injector.</p>
<p>We created the controller in the test environment, as follows:</p>
<ul>
<li><p>We used the <code>inject</code> helper method to inject instances of
<a href="api/ng.$rootScope"><code>$rootScope</code></a>,
<a href="api/ng.$controller"><code>$controller</code></a> and
<a href="api/ng.$httpBackend"><code>$httpBackend</code></a> services into the Jasmine's <code>beforeEach</code>
function. These instances come from an injector which is recreated from scratch for every single
test. This guarantees that each test starts from a well known starting point and each test is
isolated from the work done in other tests.</p></li>
<li><p>We created a new scope for our controller by calling <code>$rootScope.$new()</code></p></li>
<li><p>We called the injected <code>$controller</code> function passing the <code>PhoneListCtrl</code> function and the created
scope as parameters.</p></li>
</ul>
<p>Because our code now uses the <code>$http</code> service to fetch the phone list data in our controller, before
we create the <code>PhoneListCtrl</code> child scope, we need to tell the testing harness to expect an
incoming request from the controller. To do this we:</p>
<ul>
<li><p>Request <code>$httpBackend</code> service to be injected into our <code>beforeEach</code> function. This is a mock
version of the service that in a production environment facilitates all XHR and JSONP requests.
The mock version of this service allows you to write tests without having to deal with
native APIs and the global state associated with them — both of which make testing a nightmare.</p></li>
<li><p>Use the <code>$httpBackend.expectGET</code> method to train the <code>$httpBackend</code> service to expect an incoming
HTTP request and tell it what to respond with. Note that the responses are not returned until we call
the <code>$httpBackend.flush</code> method.</p></li>
</ul>
<p>Now, we will make assertions to verify that the <code>phones</code> model doesn't exist on <code>scope</code> before
the response is received:</p>
<pre class="prettyprint linenums">
it('should create "phones" model with 2 phones fetched from xhr', function() {
expect(scope.phones).toBeUndefined();
$httpBackend.flush();
expect(scope.phones).toEqual([{name: 'Nexus S'},
{name: 'Motorola DROID'}]);
});
</pre>
<ul>
<li><p>We flush the request queue in the browser by calling <code>$httpBackend.flush()</code>. This causes the
promise returned by the <code>$http</code> service to be resolved with the trained response.</p></li>
<li><p>We make the assertions, verifying that the phone model now exists on the scope.</p></li>
</ul>
<p>Finally, we verify that the default value of <code>orderProp</code> is set correctly:</p>
<pre class="prettyprint linenums">
it('should set the default value of orderProp model', function() {
expect(scope.orderProp).toBe('age');
});
});
});
</pre>
<p>You should now see the following output in the Testacular tab:</p>
<pre><code> Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)
</code></pre>
<h2>Experiments</h2>
<ul>
<li><p>At the bottom of <code>index.html</code>, add a <code>{{phones | json}}</code> binding to see the list of phones
displayed in json format.</p></li>
<li><p>In the <code>PhoneListCtrl</code> controller, pre-process the http response by limiting the number of phones
to the first 5 in the list. Use the following code in the $http callback:</p>
<pre><code> $scope.phones = data.splice(0, 5);
</code></pre></li>
</ul>
<h2>Summary</h2>
<p>Now that you have learned how easy it is to use angular services (thanks to Angular's dependency
injection), go to <a href="tutorial/step_06">step 6</a>, where you will add some
thumbnail images of phones and some links.</p>
<ul doc-tutorial-nav="5"></ul></div>

View file

@ -0,0 +1,99 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><ul doc-tutorial-nav="6"></ul>
<p>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.</p>
<div doc-tutorial-reset="6">
</div>
<p>You should now see links and images of the phones in the list.</p>
<p>The most important changes are listed below. You can see the full diff on <a href="https://github.com/angular/angular-phonecat/compare/step-5...step-6">GitHub</a>:</p>
<h3>Data</h3>
<p>Note that the <code>phones.json</code> file contains unique ids and image urls for each of the phones. The
urls point to the <code>app/img/phones/</code> directory.</p>
<p><strong><code>app/phones/phones.json</code></strong> (sample snippet):
<pre class="prettyprint linenums">
[
{
...
"id": "motorola-defy-with-motoblur",
"imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg",
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
...
},
...
]
</pre>
<h3>Template</h3>
<p><strong><code>app/index.html</code>:</strong>
<pre class="prettyprint linenums">
...
&lt;ul class="phones"&gt;
&lt;li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail"&gt;
&lt;a href="#/phones/{{phone.id}}" class="thumb"&gt;&lt;img ng-src="{{phone.imageUrl}}"&gt;&lt;/a&gt;
&lt;a href="#/phones/{{phone.id}}"&gt;{{phone.name}}&lt;/a&gt;
&lt;p&gt;{{phone.snippet}}&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
...
</pre>
<p>To dynamically generate links that will in the future lead to phone detail pages, we used the
now-familiar double-curly brace binding in the <code>href</code> attribute values. In step 2, we added the
<code>{{phone.name}}</code> binding as the element content. In this step the <code>{{phone.id}}</code> binding is used in
the element attribute.</p>
<p>We also added phone images next to each record using an image tag with the <a href="api/ng.directive:ngSrc"><code>ngSrc</code></a> directive. That directive prevents the
browser from treating the angular <code>{{ expression }}</code> markup literally, and initiating a request to
invalid url <code>http://localhost:8000/app/{{phone.imageUrl}}</code>, which it would have done if we had only
specified an attribute binding in a regular <code>src</code> attribute (<code>&lt;img class="diagram" src="{{phone.imageUrl}}"&gt;</code>).
Using the <code>ngSrc</code> directive prevents the browser from making an http request to an invalid location.</p>
<h3>Test</h3>
<p><strong><code>test/e2e/scenarios.js</code></strong>:
<pre class="prettyprint linenums">
...
it('should render phone specific links', function() {
input('query').enter('nexus');
element('.phones li a').click();
expect(browser().location().url()).toBe('/phones/nexus-s');
});
...
</pre>
<p>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.</p>
<p>You can now rerun <code>./scripts/e2e-test.sh</code> or refresh the browser tab with the end-to-end test
runner to see the tests run, or you can see them running on <a href="http://angular.github.com/angular-phonecat/step-6/test/e2e/runner.html">Angular's server</a>.</p>
<h2>Experiments</h2>
<ul>
<li><p>Replace the <code>ng-src</code> directive with a plain old <code>src</code> attribute. Using tools such as Firebug,
or Chrome's Web Inspector, or inspecting the webserver access logs, confirm that the app is indeed
making an extraneous request to <code>/app/%7B%7Bphone.imageUrl%7D%7D</code> (or
<code>/app/{{phone.imageUrl}}</code>).</p>
<p>The issue here is that the browser will fire a request for that invalid image address as soon as
it hits the <code>img</code> tag, which is before Angular has a chance to evaluate the expression and inject
the valid address.</p></li>
</ul>
<h2>Summary</h2>
<p>Now that you have added phone images and links, go to <a href="tutorial/step_07">step 7</a> to learn about Angular
layout templates and how Angular makes it easy to create applications that have multiple views.</p>
<ul doc-tutorial-nav="6"></ul></div>

View file

@ -0,0 +1,245 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><ul doc-tutorial-nav="7"></ul>
<p>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.</p>
<div doc-tutorial-reset="7">
</div>
<p>Note that when you now navigate to <code>app/index.html</code>, you are redirected to <code>app/index.html#/phones</code>
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.</p>
<p>The most important changes are listed below. You can see the full diff on <a href="https://github.com/angular/angular-phonecat/compare/step-6...step-7">GitHub</a>.</p>
<h3>Multiple Views, Routing and Layout Template</h3>
<p>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
<code>index.html</code> file. The next step in building the app is to add a view that will show detailed
information about each of the devices in our list.</p>
<p>To add the detailed view, we could expand the <code>index.html</code> file to contain template code for both
views, but that would get messy very quickly. Instead, we are going to turn the <code>index.html</code>
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.</p>
<p>Application routes in Angular are declared via the
<a href="api/ng.$routeProvider"><code>$routeProvider</code></a>, which is the provider of the
<a href="api/ng.$route"><code>$route service</code></a>. 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 <a href="http://en.wikipedia.org/wiki/Deep_linking">deep linking</a>, which lets us utilize the browser's
history (back and forward navigation) and bookmarks.</p>
<h4>A Note About DI, Injector and Providers</h4>
<p>As you <a href="tutorial/step_05">noticed</a>, <a href="guide/di">dependency injection</a> (DI) is the core feature of
AngularJS, so it's important for you to understand a thing or two about how it works.</p>
<p>When the application bootstraps, Angular creates an injector that will be used for all DI stuff in
this app. The injector itself doesn't know anything about what <code>$http</code> or <code>$route</code> services do, in
fact it doesn't even know about the existence of these services unless it is configured with proper
module definitions. The sole responsibilities of the injector are to load specified module
definition(s), register all service providers defined in these modules and when asked inject
a specified function with dependencies (services) that it lazily instantiates via their providers.</p>
<p>Providers are objects that provide (create) instances of services and expose configuration APIs
that can be used to control the creation and runtime behavior of a service. In case of the <code>$route</code>
service, the <code>$routeProvider</code> exposes APIs that allow you to define routes for your application.</p>
<p>Angular modules solve the problem of removing global state from the application and provide a way
of configuring the injector. As opposed to AMD or require.js modules, Angular modules don't try to
solve the problem of script load ordering or lazy script fetching. These goals are orthogonal and
both module systems can live side by side and fulfil their goals.</p>
<h3>The App Module</h3>
<p><strong><code>app/js/app.js</code>:</strong>
<pre class="prettyprint linenums">
angular.module('phonecat', []).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/phones', {templateUrl: 'partials/phone-list.html', controller: PhoneListCtrl}).
when('/phones/:phoneId', {templateUrl: 'partials/phone-detail.html', controller: PhoneDetailCtrl}).
otherwise({redirectTo: '/phones'});
}]);
</pre>
<p>In order to configure our application with routes, we need to create a module for our application.
We call this module <code>phonecat</code> and using the <code>config</code> API we request the <code>$routeProvider</code> to be
injected into our config function and use <code>$routeProvider.when</code> API to define our routes.</p>
<p>Note that during the injector configuration phase, the providers can be injected as well, but they
will not be available for injection once the injector is created and starts creating service
instances.</p>
<p>Our application routes were defined as follows:</p>
<ul>
<li><p>The phone list view will be shown when the URL hash fragment is <code>/phones</code>. To construct this
view, Angular will use the <code>phone-list.html</code> template and the <code>PhoneListCtrl</code> controller.</p></li>
<li><p>The phone details view will be shown when the URL hash fragment matches '/phone/:phoneId', where
<code>:phoneId</code> is a variable part of the URL. To construct the phone details view, angular will use the
<code>phone-detail.html</code> template and the <code>PhoneDetailCtrl</code> controller.</p></li>
</ul>
<p>We reused the <code>PhoneListCtrl</code> controller that we constructed in previous steps and we added a new,
empty <code>PhoneDetailCtrl</code> controller to the <code>app/js/controllers.js</code> file for the phone details view.</p>
<p>The statement <code>$route.otherwise({redirectTo: '/phones'})</code> triggers a redirection to <code>/phones</code> when
the browser address doesn't match either of our routes.</p>
<p>Note the use of the <code>:phoneId</code> parameter in the second route declaration. The <code>$route</code> service uses
the route declaration — <code>'/phones/:phoneId'</code> — as a template that is matched against the current
URL. All variables defined with the <code>:</code> notation are extracted into the
<a href="api/ng.$routeParams"><code>$routeParams</code></a> object.</p>
<p>In order for our application to bootstrap with our newly created module we'll also need to specify
the module name as the value of the <a href="api/ng.directive:ngApp"><code>ngApp</code></a>
directive:</p>
<p><strong><code>app/index.html</code>:</strong>
<pre class="prettyprint linenums">
&lt;!doctype html&gt;
&lt;html lang="en" ng-app="phonecat"&gt;
...
</pre>
<h3>Controllers</h3>
<p><strong><code>app/js/controllers.js</code>:</strong>
<pre class="prettyprint linenums">
...
function PhoneDetailCtrl($scope, $routeParams) {
$scope.phoneId = $routeParams.phoneId;
}
//PhoneDetailCtrl.$inject = ['$scope', '$routeParams'];
</pre>
<h3>Template</h3>
<p>The <code>$route</code> service is usually used in conjunction with the <a href="api/ng.directive:ngView"><code>ngView</code></a> directive. The role of the <code>ngView</code> directive is to include the view template for the current
route into the layout template, which makes it a perfect fit for our <code>index.html</code> template.</p>
<p><strong><code>app/index.html</code>:</strong>
<pre class="prettyprint linenums">
&lt;html lang="en" ng-app="phonecat"&gt;
&lt;head&gt;
...
&lt;script src="lib/angular/angular.js"&gt;&lt;/script&gt;
&lt;script src="js/app.js"&gt;&lt;/script&gt;
&lt;script src="js/controllers.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div ng-view&gt;&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<p>Note that we removed most of the code in the <code>index.html</code> template and replaced it with a single
line containing a div with the <code>ng-view</code> attribute. The code that we removed was placed into the
<code>phone-list.html</code> template:</p>
<p><strong><code>app/partials/phone-list.html</code>:</strong>
<pre class="prettyprint linenums">
&lt;div class="container-fluid"&gt;
&lt;div class="row-fluid"&gt;
&lt;div class="span2"&gt;
&lt;!--Sidebar content--&gt;
Search: &lt;input ng-model="query"&gt;
Sort by:
&lt;select ng-model="orderProp"&gt;
&lt;option value="name"&gt;Alphabetical&lt;/option&gt;
&lt;option value="age"&gt;Newest&lt;/option&gt;
&lt;/select&gt;
&lt;/div&gt;
&lt;div class="span10"&gt;
&lt;!--Body content--&gt;
&lt;ul class="phones"&gt;
&lt;li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail"&gt;
&lt;a href="#/phones/{{phone.id}}" class="thumb"&gt;&lt;img ng-src="{{phone.imageUrl}}"&gt;&lt;/a&gt;
&lt;a href="#/phones/{{phone.id}}"&gt;{{phone.name}}&lt;/a&gt;
&lt;p&gt;{{phone.snippet}}&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</pre>
<div style="display:none">
TODO!
<img class="diagram" src="img/tutorial/tutorial_07_final.png">
</div>
<p>We also added a placeholder template for the phone details view:</p>
<p><strong><code>app/partials/phone-detail.html</code>:</strong>
<pre class="prettyprint linenums">
TBD: detail view for {{phoneId}}
</pre>
<p>Note how we are using <code>phoneId</code> model defined in the <code>PhoneDetailCtrl</code> controller.</p>
<h3>Test</h3>
<p>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.</p>
<pre class="prettyprint linenums">
...
it('should redirect index.html to index.html#/phones', function() {
browser().navigateTo('../../app/index.html');
expect(browser().location().url()).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('phoneId')).toBe('nexus-s');
});
});
</pre>
<p>You can now rerun <code>./scripts/e2e-test.sh</code> or refresh the browser tab with the end-to-end test
runner to see the tests run, or you can see them running on <a href="http://angular.github.com/angular-phonecat/step-7/test/e2e/runner.html">Angular's server</a>.</p>
<h2>Experiments</h2>
<ul>
<li>Try to add an <code>{{orderProp}}</code> binding to <code>index.html</code>, and you'll see that nothing happens even
when you are in the phone list view. This is because the <code>orderProp</code> model is visible only in the
scope managed by <code>PhoneListCtrl</code>, which is associated with the <code>&lt;div ng-view&gt;</code> element. If you add
the same binding into the <code>phone-list.html</code> template, the binding will work as expected.</li>
</ul>
<div style="display: none">
* In `PhoneCatCtrl`, create a new model called "`hero`" with `this.hero = 'Zoro'`. In
`PhoneListCtrl` let's shadow it with `this.hero = 'Batman'`, and in `PhoneDetailCtrl` we'll use
`this.hero = "Captain Proton"`. Then add the `<p>hero = {{hero}}</p>` to all three of our templates
(`index.html`, `phone-list.html`, and `phone-detail.html`). Open the app and you'll see scope
inheritance and model property shadowing do some wonders.
</div>
<h2>Summary</h2>
<p>With the routing set up and the phone list view implemented, we're ready to go to <a href="tutorial/step_08">step 8</a> to implement the phone details view.</p>
<ul doc-tutorial-nav="7"></ul></div>

View file

@ -0,0 +1,181 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><ul doc-tutorial-nav="8"></ul>
<p>In this step, you will implement the phone details view, which is displayed when a user clicks on a
phone in the phone list.</p>
<div doc-tutorial-reset="8">
</div>
<p>Now when you click on a phone on the list, the phone details page with phone-specific information
is displayed.</p>
<p>To implement the phone details view we will use <a href="api/ng.$http"><code>$http</code></a> to fetch
our data, and we'll flesh out the <code>phone-detail.html</code> view template.</p>
<p>The most important changes are listed below. You can see the full diff on <a href="https://github.com/angular/angular-phonecat/compare/step-7...step-8">GitHub</a>:</p>
<h3>Data</h3>
<p>In addition to <code>phones.json</code>, the <code>app/phones/</code> directory also contains one json file for each
phone:</p>
<p><strong><code>app/phones/nexus-s.json</code>:</strong> (sample snippet)
<pre class="prettyprint linenums">
{
"additionalFeatures": "Contour Display, Near Field Communications (NFC),...",
"android": {
"os": "Android 2.3",
"ui": "Android"
},
...
"images": [
"img/phones/nexus-s.0.jpg",
"img/phones/nexus-s.1.jpg",
"img/phones/nexus-s.2.jpg",
"img/phones/nexus-s.3.jpg"
],
"storage": {
"flash": "16384MB",
"ram": "512MB"
}
}
</pre>
<p>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.</p>
<h3>Controller</h3>
<p>We'll expand the <code>PhoneDetailCtrl</code> by using the <code>$http</code> service to fetch the json files. This works
the same way as the phone list controller.</p>
<p><strong><code>app/js/controllers.js</code>:</strong>
<pre class="prettyprint linenums">
function PhoneDetailCtrl($scope, $routeParams, $http) {
$http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
$scope.phone = data;
});
}
//PhoneDetailCtrl.$inject = ['$scope', '$routeParams', '$http'];
</pre>
<p>To construct the URL for the HTTP request, we use <code>$routeParams.phoneId</code> extracted from the current
route by the <code>$route</code> service.</p>
<h3>Template</h3>
<p>The TBD placeholder line has been replaced with lists and bindings that comprise the phone details.
Note where we use the angular <code>{{expression}}</code> markup and <code>ngRepeat</code> to project phone data from
our model into the view.</p>
<p><strong><code>app/partials/phone-detail.html</code>:</strong>
<pre class="prettyprint linenums">
&lt;img ng-src="{{phone.images[0]}}" class="phone"&gt;
&lt;h1&gt;{{phone.name}}&lt;/h1&gt;
&lt;p&gt;{{phone.description}}&lt;/p&gt;
&lt;ul class="phone-thumbs"&gt;
&lt;li ng-repeat="img in phone.images"&gt;
&lt;img ng-src="{{img}}"&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul class="specs"&gt;
&lt;li&gt;
&lt;span&gt;Availability and Networks&lt;/span&gt;
&lt;dl&gt;
&lt;dt&gt;Availability&lt;/dt&gt;
&lt;dd ng-repeat="availability in phone.availability"&gt;{{availability}}&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
...
&lt;/li&gt;
&lt;span&gt;Additional Features&lt;/span&gt;
&lt;dd&gt;{{phone.additionalFeatures}}&lt;/dd&gt;
&lt;/li&gt;
&lt;/ul&gt;
</pre>
<div style="display: none">
TODO!
<img class="diagram" src="img/tutorial/tutorial_08-09_final.png">
</div>
<h3>Test</h3>
<p>We wrote a new unit test that is similar to the one we wrote for the <code>PhoneListCtrl</code> controller in
step 5.</p>
<p><strong><code>test/unit/controllersSpec.js</code>:</strong>
<pre class="prettyprint linenums">
...
describe('PhoneDetailCtrl', function(){
var scope, $httpBackend, ctrl;
beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/xyz.json').respond({name:'phone xyz'});
$routeParams.phoneId = 'xyz';
scope = $rootScope.$new();
ctrl = $controller(PhoneDetailCtrl, {$scope: scope});
}));
it('should fetch phone detail', function() {
expect(scope.phone).toBeUndefined();
$httpBackend.flush();
expect(scope.phone).toEqual({name:'phone xyz'});
});
});
...
</pre>
<p>You should now see the following output in the Testacular tab:</p>
<pre><code>Chrome 22.0: Executed 3 of 3 SUCCESS (0.039 secs / 0.012 secs)
</code></pre>
<p>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".</p>
<p><strong><code>test/e2e/scenarios.js</code>:</strong>
<pre class="prettyprint linenums">
...
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>
<p>You can now rerun <code>./scripts/e2e-test.sh</code> or refresh the browser tab with the end-to-end test
runner to see the tests run, or you can see them running on <a href="http://angular.github.com/angular-phonecat/step-8/test/e2e/runner.html">Angular's server</a>.</p>
<h2>Experiments</h2>
<ul>
<li>Using the <a href="guide/dev_guide.e2e-testing">Angular's end-to-end test runner API</a>, write a test
that verifies that we display 4 thumbnail images on the Nexus S details page.</li>
</ul>
<h2>Summary</h2>
<p>Now that the phone details view is in place, proceed to <a href="tutorial/step_09">step 9</a> to learn how to
write your own custom display filter.</p>
<ul doc-tutorial-nav="8"></ul></div>

View file

@ -0,0 +1,131 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><ul doc-tutorial-nav="9"></ul>
<p>In this step you will learn how to create your own custom display filter.</p>
<div doc-tutorial-reset="9">
</div>
<p>Navigate to one of the detail pages.</p>
<p>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.</p>
<p>The most important changes are listed below. You can see the full diff on <a href="https://github.com/angular/angular-phonecat/compare/step-8...step-9">GitHub</a>:</p>
<h3>Custom Filter</h3>
<p>In order to create a new filter, you are going to create a <code>phonecatFilters</code> module and register
your custom filter with this module:</p>
<p><strong><code>app/js/filters.js</code>:</strong>
<pre class="prettyprint linenums">
angular.module('phonecatFilters', []).filter('checkmark', function() {
return function(input) {
return input ? '\u2713' : '\u2718';
};
});
</pre>
<p>The name of our filter is "checkmark". The <code>input</code> evaluates to either <code>true</code> or <code>false</code>, and we
return one of two unicode characters we have chosen to represent true or false (<code>\u2713</code> and
<code>\u2718</code>).</p>
<p>Now that our filter is ready, we need to register the <code>phonecatFilters</code> module as a dependency for
our main <code>phonecat</code> module.</p>
<p><strong><code>app/js/app.js</code>:</strong>
<pre class="prettyprint linenums">
...
angular.module('phonecat', ['phonecatFilters']).
...
</pre>
<h3>Template</h3>
<p>Since the filter code lives in the <code>app/js/filters.js</code> file, we need to include this file in our
layout template.</p>
<p><strong><code>app/index.html</code>:</strong>
<pre class="prettyprint linenums">
...
&lt;script src="js/controllers.js"&gt;&lt;/script&gt;
&lt;script src="js/filters.js"&gt;&lt;/script&gt;
...
</pre>
<p>The syntax for using filters in Angular templates is as follows:</p>
<pre><code>{{ expression | filter }}
</code></pre>
<p>Let's employ the filter in the phone details template:</p>
<p><strong><code>app/partials/phone-detail.html</code>:</strong>
<pre class="prettyprint linenums">
...
&lt;dl&gt;
&lt;dt&gt;Infrared&lt;/dt&gt;
&lt;dd&gt;{{phone.connectivity.infrared | checkmark}}&lt;/dd&gt;
&lt;dt&gt;GPS&lt;/dt&gt;
&lt;dd&gt;{{phone.connectivity.gps | checkmark}}&lt;/dd&gt;
&lt;/dl&gt;
...
</pre>
<h3>Test</h3>
<p>Filters, like any other component, should be tested and these tests are very easy to write.</p>
<p><strong><code>test/unit/filtersSpec.js</code>:</strong>
<pre class="prettyprint linenums">
describe('filter', function() {
beforeEach(module('phonecatFilters'));
describe('checkmark', function() {
it('should convert boolean values to unicode checkmark or cross',
inject(function(checkmarkFilter) {
expect(checkmarkFilter(true)).toBe('\u2713');
expect(checkmarkFilter(false)).toBe('\u2718');
}));
});
});
</pre>
<p>Note that you need to configure our test injector with the <code>phonecatFilters</code> module before any of
our filter tests execute.</p>
<p>You should now see the following output in the Testacular tab:</p>
<pre><code> Chrome 22.0: Executed 4 of 4 SUCCESS (0.034 secs / 0.012 secs)
</code></pre>
<h2>Experiments</h2>
<ul>
<li><p>Let's experiment with some of the <a href="api/ng.$filter"><code>built-in Angular filters</code></a> and add the
following bindings to <code>index.html</code>:</p>
<ul><li><code>{{ "lower cap string" | uppercase }}</code></li>
<li><code>{{ {foo: "bar", baz: 23} | json }}</code></li>
<li><code>{{ 1304375948024 | date }}</code></li>
<li><code>{{ 1304375948024 | date:"MM/dd/yyyy @ h:mma" }}</code></li></ul></li>
<li><p>We can also create a model with an input element, and combine it with a filtered binding. Add
the following to index.html:</p>
<pre><code>&lt;input ng-model="userInput"&gt; Uppercased: {{ userInput | uppercase }}
</code></pre></li>
</ul>
<h2>Summary</h2>
<p>Now that you have learned how to write and test a custom filter, go to <a href="tutorial/step_10">step 10</a> to
learn how we can use Angular to enhance the phone details page further.</p>
<ul doc-tutorial-nav="9"></ul></div>

View file

@ -0,0 +1,134 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><ul doc-tutorial-nav="10"></ul>
<p>In this step, you will add a clickable phone image swapper to the phone details page.</p>
<div doc-tutorial-reset="10">
</div>
<p>The phone details view displays one large image of the current phone and several smaller thumbnail
images. It would be great if we could replace the large image with any of the thumbnails just by
clicking on the desired thumbnail image. Let's have a look at how we can do this with Angular.</p>
<p>The most important changes are listed below. You can see the full diff on <a href="https://github.com/angular/angular-phonecat/compare/step-9...step-10">GitHub</a>:</p>
<h3>Controller</h3>
<p><strong><code>app/js/controllers.js</code>:</strong>
<pre class="prettyprint linenums">
...
function PhoneDetailCtrl($scope, $routeParams, $http) {
$http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
$scope.phone = data;
$scope.mainImageUrl = data.images[0];
});
$scope.setImage = function(imageUrl) {
$scope.mainImageUrl = imageUrl;
}
}
//PhoneDetailCtrl.$inject = ['$scope', '$routeParams', '$http'];
</pre>
<p>In the <code>PhoneDetailCtrl</code> controller, we created the <code>mainImageUrl</code> model property and set its
default value to the first phone image URL.</p>
<p>We also created a <code>setImage</code> event handler function that will change the value of <code>mainImageUrl</code>.</p>
<h3>Template</h3>
<p><strong><code>app/partials/phone-detail.html</code>:</strong>
<pre class="prettyprint linenums">
&lt;img ng-src="{{mainImageUrl}}" class="phone"&gt;
...
&lt;ul class="phone-thumbs"&gt;
&lt;li ng-repeat="img in phone.images"&gt;
&lt;img ng-src="{{img}}" ng-click="setImage(img)"&gt;
&lt;/li&gt;
&lt;/ul&gt;
...
</pre>
<p>We bound the <code>ngSrc</code> directive of the large image to the <code>mainImageUrl</code> property.</p>
<p>We also registered an <a href="api/ng.directive:ngClick"><code><code>ngClick</code></code></a>
handler with thumbnail images. When a user clicks on one of the thumbnail images, the handler will
use the <code>setImage</code> event handler function to change the value of the <code>mainImageUrl</code> property to the
URL of the thumbnail image.</p>
<div style="display: none">
TODO!
<img class="diagram" src="img/tutorial/tutorial_10-11_final.png">
</div>
<h3>Test</h3>
<p>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.</p>
<p><strong><code>test/e2e/scenarios.js</code>:</strong>
<pre class="prettyprint linenums">
...
describe('Phone detail view', function() {
...
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');
});
it('should swap main image if a thumbnail image is clicked on', function() {
element('.phone-thumbs li:nth-child(3) img').click();
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.2.jpg');
element('.phone-thumbs li:nth-child(1) img').click();
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
});
});
});
</pre>
<p>You can now rerun <code>./scripts/e2e-test.sh</code> or refresh the browser tab with the end-to-end test
runner to see the tests run, or you can see them running on <a href="http://angular.github.com/angular-phonecat/step-8/test/e2e/runner.html">Angular's server</a>.</p>
<h2>Experiments</h2>
<ul>
<li><p>Let's add a new controller method to <code>PhoneDetailCtrl</code>:</p>
<pre><code> $scope.hello = function(name) {
alert('Hello ' + (name || 'world') + '!');
}
</code></pre>
<p>and add:</p>
<pre><code> &lt;button ng-click="hello('Elmo')"&gt;Hello&lt;/button&gt;
</code></pre>
<p>to the <code>phone-details.html</code> template.</p></li>
</ul>
<div style="display: none">
TODO!
The controller methods are inherited between controllers/scopes, so you can use the same snippet
in the `phone-list.html` template as well.
* Move the `hello` method from `PhoneCatCtrl` to `PhoneListCtrl` and you'll see that the button
declared in `index.html` will stop working, while the one declared in the `phone-list.html`
template remains operational.
</div>
<h2>Summary</h2>
<p>With the phone image swapper in place, we're ready for <a href="tutorial/step_11">step 11</a> (the last step!) to
learn an even better way to fetch data.</p>
<ul doc-tutorial-nav="10"></ul></div>

View file

@ -0,0 +1,215 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><ul doc-tutorial-nav="11"></ul>
<p>In this step, you will improve the way our app fetches data.</p>
<div doc-tutorial-reset="11">
</div>
<p>The last improvement we will make to our app is to define a custom service that represents a <a href="http://en.wikipedia.org/wiki/Representational_State_Transfer">RESTful</a> client. Using this client we
can make XHR requests for data in an easier way, without having to deal with the lower-level <a href="api/ng.$http"><code>$http</code></a> API, HTTP methods and URLs.</p>
<p>The most important changes are listed below. You can see the full diff on <a href="https://github.com/angular/angular-phonecat/compare/step-10...step-11">GitHub</a>:</p>
<h3>Template</h3>
<p>The custom service is defined in <code>app/js/services.js</code> so we need to include this file in our layout
template. Additionally, we also need to load the <code>angular-resource.js</code> file, which contains the
<code>ngResource</code> module and in it the <code>$resource</code> service, that we'll soon use:</p>
<p><strong><code>app/index.html</code>.</strong>
<pre class="prettyprint linenums">
...
&lt;script src="js/services.js"&gt;&lt;/script&gt;
&lt;script src="lib/angular/angular-resource.js"&gt;&lt;/script&gt;
...
</pre>
<h3>Service</h3>
<p><strong><code>app/js/services.js</code>.</strong>
<pre class="prettyprint linenums">
angular.module('phonecatServices', ['ngResource']).
factory('Phone', function($resource){
return $resource('phones/:phoneId.json', {}, {
query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
});
});
</pre>
<p>We used the module API to register a custom service using a factory function. We passed in the name
of the service - 'Phone' - and the factory function. The factory function is similar to a
controller's constructor in that both can declare dependencies via function arguments. The Phone
service declared a dependency on the <code>$resource</code> service.</p>
<p>The <a href="api/ngResource.$resource"><code>$resource</code></a> service makes it easy to create a
<a href="http://en.wikipedia.org/wiki/Representational_State_Transfer">RESTful</a> client with just a few
lines of code. This client can then be used in our application, instead of the lower-level <a href="api/ng.$http"><code>$http</code></a> service.</p>
<p><strong><code>app/js/app.js</code>.</strong>
<pre class="prettyprint linenums">
...
angular.module('phonecat', ['phonecatFilters', 'phonecatServices']).
...
</pre>
<p>We need to add 'phonecatServices' to 'phonecat' application's requires array.</p>
<h3>Controller</h3>
<p>We simplified our sub-controllers (<code>PhoneListCtrl</code> and <code>PhoneDetailCtrl</code>) by factoring out the
lower-level <a href="api/ng.$http"><code>$http</code></a> service, replacing it with a new service called
<code>Phone</code>. Angular's <a href="api/ngResource.$resource"><code>$resource</code></a> service is easier to
use than <code>$http</code> for interacting with data sources exposed as RESTful resources. It is also easier
now to understand what the code in our controllers is doing.</p>
<p><strong><code>app/js/controllers.js</code>.</strong>
<pre class="prettyprint linenums">
...
function PhoneListCtrl($scope, Phone) {
$scope.phones = Phone.query();
$scope.orderProp = 'age';
}
//PhoneListCtrl.$inject = ['$scope', 'Phone'];
function PhoneDetailCtrl($scope, $routeParams, Phone) {
$scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
$scope.mainImageUrl = phone.images[0];
});
$scope.setImage = function(imageUrl) {
$scope.mainImageUrl = imageUrl;
}
}
//PhoneDetailCtrl.$inject = ['$scope', '$routeParams', 'Phone'];
</pre>
<p>Notice how in <code>PhoneListCtrl</code> we replaced:</p>
<pre><code>$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
</code></pre>
<p>with:</p>
<pre><code>$scope.phones = Phone.query();
</code></pre>
<p>This is a simple statement that we want to query for all phones.</p>
<p>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.</p>
<p>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
<code>PhoneDetailCtrl</code> controller illustrates this by setting the <code>mainImageUrl</code> in a callback.</p>
<h3>Test</h3>
<p>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.</p>
<p>The <a href="api/ngResource.$resource">$resource</a> service augments the response object
with methods for updating and deleting the resource. If we were to use the standard <code>toEqual</code>
matcher, our tests would fail because the test values would not match the responses exactly. To
solve the problem, we use a newly-defined <code>toEqualData</code> <a href="http://pivotal.github.com/jasmine/jsdoc/symbols/jasmine.Matchers.html">Jasmine matcher</a>. When the
<code>toEqualData</code> matcher compares two objects, it takes only object properties into account and
ignores methods.</p>
<p><strong><code>test/unit/controllersSpec.js</code>:</strong>
<pre class="prettyprint linenums">
describe('PhoneCat controllers', function() {
beforeEach(function(){
this.addMatchers({
toEqualData: function(expected) {
return angular.equals(this.actual, expected);
}
});
});
beforeEach(module('phonecatServices'));
describe('PhoneListCtrl', function(){
var scope, ctrl, $httpBackend;
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/phones.json').
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
scope = $rootScope.$new();
ctrl = $controller(PhoneListCtrl, {$scope: scope});
}));
it('should create "phones" model with 2 phones fetched from xhr', function() {
expect(scope.phones).toEqual([]);
$httpBackend.flush();
expect(scope.phones).toEqualData(
[{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
});
it('should set the default value of orderProp model', function() {
expect(scope.orderProp).toBe('age');
});
});
describe('PhoneDetailCtrl', function(){
var scope, $httpBackend, ctrl,
xyzPhoneData = function() {
return {
name: 'phone xyz',
images: ['image/url1.png', 'image/url2.png']
}
};
beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData());
$routeParams.phoneId = 'xyz';
scope = $rootScope.$new();
ctrl = $controller(PhoneDetailCtrl, {$scope: scope});
}));
it('should fetch phone detail', function() {
expect(scope.phone).toEqualData({});
$httpBackend.flush();
expect(scope.phone).toEqualData(xyzPhoneData());
});
});
});
</pre>
<p>You should now see the following output in the Testacular tab:</p>
<pre><code>Chrome 22.0: Executed 4 of 4 SUCCESS (0.038 secs / 0.01 secs)
</code></pre>
<h2>Summary</h2>
<p>There you have it! We have created a web app in a relatively short amount of time. In the <a href="tutorial/the_end">closing notes</a> we'll cover where to go from here.</p>
<ul doc-tutorial-nav="11"></ul></div>

View file

@ -0,0 +1,19 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><p>Our application is now complete. Feel free to experiment with the code further, and jump back to
previous steps using the <code>git checkout</code> command.</p>
<p>For more details and examples of the Angular concepts we touched on in this tutorial, see the
<a href="guide/index">Developer Guide</a>.</p>
<p>For several more examples of code, see the <a href="cookbook/index">Cookbook</a>.</p>
<p>When you are ready to start developing a project using Angular, we recommend that you bootstrap
your development with the <a href="https://github.com/angular/angular-seed">angular-seed</a> project.</p>
<p>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 <a href="misc/contribute">contributing</a> to Angular.</p>
<p>If you have questions or feedback or just want to say "hi", please post a message at <a href="https://groups.google.com/forum/#!forum/angular">https://groups.google.com/forum/#!forum/angular</a>.</p></div>