Update everything
This commit is contained in:
parent
bf368181a4
commit
72a485d6e8
319 changed files with 67958 additions and 13948 deletions
112
lib/angular/docs/partials/tutorial/index.html
Normal file
112
lib/angular/docs/partials/tutorial/index.html
Normal 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>
|
206
lib/angular/docs/partials/tutorial/step_00.html
Normal file
206
lib/angular/docs/partials/tutorial/step_00.html
Normal 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">
|
||||
<!doctype html>
|
||||
<html lang="en" ng-app>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>My HTML File</title>
|
||||
<link rel="stylesheet" href="css/app.css">
|
||||
<link rel="stylesheet" href="css/bootstrap.css">
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Nothing here {{'yet' + '!'}}</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
<h3>What is the code doing?</h3>
|
||||
|
||||
<ul>
|
||||
<li><p><code>ng-app</code> directive:</p>
|
||||
|
||||
<pre><code> <html ng-app>
|
||||
</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> <script src="lib/angular/angular.js">
|
||||
</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> <p>1 + 2 = {{ 1 + 2 }}</p>
|
||||
</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>
|
50
lib/angular/docs/partials/tutorial/step_01.html
Normal file
50
lib/angular/docs/partials/tutorial/step_01.html
Normal 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">
|
||||
<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>
|
||||
|
||||
<h2>Experiments</h2>
|
||||
|
||||
<ul>
|
||||
<li><p>Try adding more static HTML to <code>index.html</code>. For example:</p>
|
||||
|
||||
<pre><code> <p>Total number of phones: 2</p>
|
||||
</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>
|
194
lib/angular/docs/partials/tutorial/step_02.html
Normal file
194
lib/angular/docs/partials/tutorial/step_02.html
Normal 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/Model–View–Controller">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">
|
||||
<html ng-app>
|
||||
<head>
|
||||
...
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
<script src="js/controllers.js"></script>
|
||||
</head>
|
||||
<body ng-controller="PhoneListCtrl">
|
||||
|
||||
<ul>
|
||||
<li ng-repeat="phone in phones">
|
||||
{{phone.name}}
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
</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><li></code> tag is an Angular repeater. The
|
||||
repeater tells Angular to create a <code><li></code> element for each phone in the list using the first <code><li></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><body></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><body ng-controller="PhoneListCtrl"></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> <p>Total number of phones: {{phones.length}}</p>
|
||||
</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> <table>
|
||||
<tr><th>row number</th></tr>
|
||||
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i}}</td></tr>
|
||||
</table>
|
||||
</code></pre>
|
||||
|
||||
<p>Now, make the list 1-based by incrementing <code>i</code> by one in the binding:</p>
|
||||
|
||||
<pre><code> <table>
|
||||
<tr><th>row number</th></tr>
|
||||
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
|
||||
</table>
|
||||
</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>
|
196
lib/angular/docs/partials/tutorial/step_03.html
Normal file
196
lib/angular/docs/partials/tutorial/step_03.html
Normal 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">
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
<!--Sidebar content-->
|
||||
|
||||
Search: <input ng-model="query">
|
||||
|
||||
</div>
|
||||
<div class="span10">
|
||||
<!--Body content-->
|
||||
|
||||
<ul class="phones">
|
||||
<li ng-repeat="phone in phones | filter:query">
|
||||
{{phone.name}}
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
<p>We added a standard HTML <code><input></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> <title>Google Phone Gallery: {{query}}</title>
|
||||
</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> <body ng-controller="PhoneListCtrl">
|
||||
</code></pre>
|
||||
|
||||
<p>If you want to bind to the query model from the <code><title></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> <html ng-app ng-controller="PhoneListCtrl">
|
||||
</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> <title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>
|
||||
</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> <div id="status">Current filter: {{query}}</div>
|
||||
</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>
|
178
lib/angular/docs/partials/tutorial/step_04.html
Normal file
178
lib/angular/docs/partials/tutorial/step_04.html
Normal 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: <input ng-model="query">
|
||||
Sort by:
|
||||
<select ng-model="orderProp">
|
||||
<option value="name">Alphabetical</option>
|
||||
<option value="age">Newest</option>
|
||||
</select>
|
||||
|
||||
|
||||
<ul class="phones">
|
||||
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp">
|
||||
{{phone.name}}
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</pre>
|
||||
|
||||
<p>We made the following changes to the <code>index.html</code> template:</p>
|
||||
|
||||
<ul>
|
||||
<li><p>First, we added a <code><select></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>
|
227
lib/angular/docs/partials/tutorial/step_05.html
Normal file
227
lib/angular/docs/partials/tutorial/step_05.html
Normal 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>
|
99
lib/angular/docs/partials/tutorial/step_06.html
Normal file
99
lib/angular/docs/partials/tutorial/step_06.html
Normal 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">
|
||||
...
|
||||
<ul class="phones">
|
||||
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
|
||||
<a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
|
||||
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
...
|
||||
</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><img class="diagram" src="{{phone.imageUrl}}"></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>
|
245
lib/angular/docs/partials/tutorial/step_07.html
Normal file
245
lib/angular/docs/partials/tutorial/step_07.html
Normal 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">
|
||||
<!doctype html>
|
||||
<html lang="en" ng-app="phonecat">
|
||||
...
|
||||
</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">
|
||||
<html lang="en" ng-app="phonecat">
|
||||
<head>
|
||||
...
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/controllers.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div ng-view></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</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">
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
<!--Sidebar content-->
|
||||
|
||||
Search: <input ng-model="query">
|
||||
Sort by:
|
||||
<select ng-model="orderProp">
|
||||
<option value="name">Alphabetical</option>
|
||||
<option value="age">Newest</option>
|
||||
</select>
|
||||
|
||||
</div>
|
||||
<div class="span10">
|
||||
<!--Body content-->
|
||||
|
||||
<ul class="phones">
|
||||
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
|
||||
<a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
|
||||
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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><div ng-view></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>
|
181
lib/angular/docs/partials/tutorial/step_08.html
Normal file
181
lib/angular/docs/partials/tutorial/step_08.html
Normal 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">
|
||||
<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>
|
||||
|
||||
<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>
|
131
lib/angular/docs/partials/tutorial/step_09.html
Normal file
131
lib/angular/docs/partials/tutorial/step_09.html
Normal 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">
|
||||
...
|
||||
<script src="js/controllers.js"></script>
|
||||
<script src="js/filters.js"></script>
|
||||
...
|
||||
</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">
|
||||
...
|
||||
<dl>
|
||||
<dt>Infrared</dt>
|
||||
<dd>{{phone.connectivity.infrared | checkmark}}</dd>
|
||||
<dt>GPS</dt>
|
||||
<dd>{{phone.connectivity.gps | checkmark}}</dd>
|
||||
</dl>
|
||||
...
|
||||
</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><input ng-model="userInput"> 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>
|
134
lib/angular/docs/partials/tutorial/step_10.html
Normal file
134
lib/angular/docs/partials/tutorial/step_10.html
Normal 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">
|
||||
<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>
|
||||
|
||||
<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> <button ng-click="hello('Elmo')">Hello</button>
|
||||
</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>
|
215
lib/angular/docs/partials/tutorial/step_11.html
Normal file
215
lib/angular/docs/partials/tutorial/step_11.html
Normal 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">
|
||||
...
|
||||
<script src="js/services.js"></script>
|
||||
<script src="lib/angular/angular-resource.js"></script>
|
||||
...
|
||||
</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>
|
19
lib/angular/docs/partials/tutorial/the_end.html
Normal file
19
lib/angular/docs/partials/tutorial/the_end.html
Normal 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>
|
Loading…
Add table
Add a link
Reference in a new issue