diff --git a/docs/content/tutorial/index.ngdoc b/docs/content/tutorial/index.ngdoc
index 78e1cfee6172..e17cae45c61c 100644
--- a/docs/content/tutorial/index.ngdoc
+++ b/docs/content/tutorial/index.ngdoc
@@ -6,12 +6,12 @@
# PhoneCat Tutorial App
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
+the construction of an Angular 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.
-
+
Follow the tutorial to see how Angular makes browsers smarter — without the use of native
extensions or plug-ins:
@@ -28,10 +28,11 @@ When you finish the tutorial you will be able to:
* Create a dynamic application that works in all modern browsers.
* Use data binding to wire up your data model to your views.
* Create and run unit tests, with Karma.
-* Create and run end to end tests, with Protractor.
-* Move application logic out of the template and into Controllers.
+* Create and run end-to-end tests, with Protractor.
+* Move application logic out of the template and into components and controllers.
* Get data from a server using Angular services.
-* Apply animations to your application, using ngAnimate.
+* Apply animations to your application, using the `ngAnimate` module.
+* Structure your Angular applications in a modular way that scales well for larger projects.
* Identify resources for learning more about AngularJS.
The tutorial guides you through the entire process of building a simple application, including
@@ -42,16 +43,18 @@ You can go through the whole tutorial in a couple of hours or you may want to sp
really digging into it. If you're looking for a shorter introduction to AngularJS, check out the
{@link misc/started Getting Started} document.
-# Get Started
+
+# Environment Setup
The rest of this page explains how you can set up your local machine for development.
-If you just want to read the tutorial then you can just go straight to the first step:
+If you just want to _read_ the tutorial, you can go straight to the first step:
[Step 0 - Bootstrapping](tutorial/step_00).
-# Working with the code
+
+## Working with the Code
You can follow along with this tutorial and hack on the code in the comfort of your own computer.
-In this way you can get hands-on practice of really writing AngularJS code and also on using the
+This way, you can get hands-on practice of really writing Angular code and also on using the
recommended testing tools.
The tutorial relies on the use of the [Git][git] versioning system for source code management.
@@ -62,10 +65,11 @@ a few git commands.
### Install Git
You can download and install Git from http://git-scm.com/download. Once installed, you should have
-access to the `git` command line tool. The main commands that you will need to use are:
+access to the `git` command line tool. The main commands that you will need to use are:
+
+* `git clone ...`: Clone a remote repository onto your local machine.
+* `git checkout ...`: Check out a particular branch or a tagged version of the code to hack on.
-- `git clone ...` : clone a remote repository onto your local machine
-- `git checkout ...` : check out a particular branch or a tagged version of the code to hack on
### Download angular-phonecat
@@ -73,13 +77,14 @@ Clone the [angular-phonecat repository][angular-phonecat] located at GitHub by r
command:
```
-git clone --depth=14 https://github.com/angular/angular-phonecat.git
+git clone --depth=16 https://github.com/angular/angular-phonecat.git
```
-This command creates the `angular-phonecat` directory in your current directory.
+This command creates an `angular-phonecat` sub-directory in your current directory.
-
The `--depth=14` option just tells Git to pull down only the last 14 commits. This makes the
-download much smaller and faster.
+
+ The `--depth=16` option tells Git to pull down only the last 16 commits.
+ This makes the download much smaller and faster.
Change your current directory to `angular-phonecat`.
@@ -88,16 +93,16 @@ Change your current directory to `angular-phonecat`.
cd angular-phonecat
```
-The tutorial instructions, from now on, assume you are running all commands from the
+The tutorial instructions, from now on, assume you are running all commands from within the
`angular-phonecat` directory.
### Install Node.js
-If you want to run the preconfigured local web-server and the test tools then you will also need
-[Node.js v0.10.27+][node].
+If you want to run the preconfigured local web server and the test tools then you will also need
+[Node.js v4+][node].
-You can download a Node.js installer for your operating system from http://nodejs.org/download/.
+You can download a Node.js installer for your operating system from https://nodejs.org/en/download/.
Check the version of Node.js that you have installed by running the following command:
@@ -105,7 +110,7 @@ Check the version of Node.js that you have installed by running the following co
node --version
```
-In Debian based distributions, there is a name clash with another utility called `node`. The
+In Debian based distributions, there might be a name clash with another utility called `node`. The
suggested solution is to also install the `nodejs-legacy` apt package, which renames `node` to
`nodejs`.
@@ -115,12 +120,9 @@ nodejs --version
npm --version
```
-
-
If you need to run different versions of node.js
- in your local environment, consider installing
-
- Node Version Manager (nvm)
- .
+
+ If you need to run different versions of Node.js in your local environment, consider installing
+ [Node Version Manager (nvm)][nvm] or [Node Version Manager (nvm) for Windows][nvm-windows].
Once you have Node.js installed on your machine, you can download the tool dependencies by running:
@@ -129,30 +131,32 @@ Once you have Node.js installed on your machine, you can download the tool depen
npm install
```
-This command reads angular-phonecat's `package.json` file and downloads the following tools
-into the `node_modules` directory:
+This command reads angular-phonecat's `package.json` file and downloads the following tools into the
+`node_modules` directory:
-- [Bower][bower] - client-side code package manager
-- [Http-Server][http-server] - simple local static web server
-- [Karma][karma] - unit test runner
-- [Protractor][protractor] - end to end (E2E) test runner
+* [Bower][bower] - client-side code package manager
+* [Http-Server][http-server] - simple local static web server
+* [Karma][karma] - unit test runner
+* [Protractor][protractor] - end-to-end (E2E) test runner
-Running `npm install` will also automatically use bower to download the Angular framework into the
+Running `npm install` will also automatically use bower to download the AngularJS framework into the
`app/bower_components` directory.
Note the angular-phonecat project is setup to install and run these utilities via npm scripts.
This means that you do not have to have any of these utilities installed globally on your system
- to follow the tutorial. See **Installing Helper Tools** below for more information.
+ to follow the tutorial. See [Installing Helper Tools](tutorial/#install-helper-tools-optional-)
+ below for more information.
The project is preconfigured with a number of npm helper scripts to make it easy to run the common
tasks that you will need while developing:
-- `npm start` : start a local development web-server
-- `npm test` : start the Karma unit test runner
-- `npm run protractor` : run the Protractor end to end (E2E) tests
-- `npm run update-webdriver` : install the drivers needed by Protractor
+* `npm start`: Start a local development web server.
+* `npm test`: Start the Karma unit test runner.
+* `npm run protractor`: Run the Protractor end-to-end (E2E) tests.
+* `npm run update-webdriver`: Install the drivers needed by Protractor.
+
### Install Helper Tools (optional)
@@ -167,7 +171,7 @@ For instance, to install the Bower command line executable you would do:
sudo npm install -g bower
```
-*(Omit the sudo if running on Windows)*
+_(Omit the sudo if running on Windows)_
Then you can run the bower tool directly, such as:
@@ -176,10 +180,10 @@ bower install
```
-### Running Development Web Server
+### Running the Development Web Server
While Angular applications are purely client-side code, and it is possible to open them in a web
-browser directly from the file system, it is better to serve them from a HTTP web server. In
+browser directly from the file system, it is better to serve them from an HTTP web server. In
particular, for security reasons, most modern browsers will not allow JavaScript to make server
requests if the page is loaded directly from the file system.
@@ -190,70 +194,64 @@ application during development. Start the web server by running:
npm start
```
-This will create a local webserver that is listening to port 8000 on your local machine.
-You can now browse to the application at:
-
-```
-http://localhost:8000/app/index.html
-```
+This will create a local web server that is listening to port 8000 on your local machine.
+You can now browse to the application at http://localhost:8000/index.html.
-To serve the web app on a different IP address or port, edit the "start" script within package.json.
-You can use `-a` to set the address and `-p` to set the port.
+ To serve the web app on a different IP address or port, edit the "start" script within
+ `package.json`. You can use `-a` to set the address and `-p` to set the port. You also need to
+ update the `baseUrl` configuration property in `e2e-test/protractor.conf.js`.
+
### Running Unit Tests
We use unit tests to ensure that the JavaScript code in our application is operating correctly.
-Unit tests focus on testing small isolated parts of the application. The unit tests are kept in the
-`test/unit` directory.
+Unit tests focus on testing small isolated parts of the application. The unit tests are kept in test
+files (specs) side-by-side with the application code. This way it's easier to find them and keep
+them up-to-date with the code under test. It also makes refactoring our app structure easier, since
+tests are moved together with the source code.
The angular-phonecat project is configured to use [Karma][karma] to run the unit tests for the
-application. Start Karma by running:
+application. Start Karma by running:
```
npm test
```
-This will start the Karma unit test runner. Karma will read the configuration file at
-`test/karma.conf.js`. This configuration file tells Karma to:
+This will start the Karma unit test runner. Karma will read the configuration file `karma.conf.js`,
+located at the root of the project directory. This configuration file tells Karma to:
-- open up a Chrome browser and connect it to Karma
-- execute all the unit tests in this browser
-- report the results of these tests in the terminal/command line window
-- watch all the project's JavaScript files and re-run the tests whenever any of these change
+* Open up instances of the Chrome and Firefox browsers and connect them to Karma.
+* Execute all the unit tests in these browsers.
+* Report the results of these tests in the terminal/command line window.
+* Watch all the project's JavaScript files and re-run the tests whenever any of these change.
It is good to leave this running all the time, in the background, as it will give you immediate
feedback about whether your changes pass the unit tests while you are working on the code.
-### Running End to End Tests
+### Running E2E Tests
-We use End to End tests to ensure that the application as a whole operates as expected.
-End to End tests are designed to test the whole client side application, in particular that the
-views are displaying and behaving correctly. It does this by simulating real user interaction with
-the real application running in the browser.
+We use E2E (end-to-end) tests to ensure that the application as a whole operates as expected.
+E2E tests are designed to test the whole client-side application, in particular that the views are
+displaying and behaving correctly. It does this by simulating real user interaction with the real
+application running in the browser.
-The End to End tests are kept in the `test/e2e` directory.
+The E2E tests are kept in the `e2e-tests` directory.
-The angular-phonecat project is configured to use [Protractor][protractor] to run the End to End
-tests for the application. Protractor relies upon a set of drivers to allow it to interact with
-the browser. You can install these drivers by running:
+The angular-phonecat project is configured to use [Protractor][protractor] to run the E2E tests for
+the application. Protractor relies upon a set of drivers to allow it to interact with the browser.
+You can install these drivers by running:
```
npm run update-webdriver
```
-*(You should only need to do this once.)*
-
-You will need to have Java present on your dev machine to allow the Selenium standalone to be started.
-Check if you already have java installed by opening a terminal/command line window and typing
-'''
-java -version
-'''
-If java is already installed and exists in the PATH then you will be shown the version installed,
-if, however you receive a message that "java is not recognized as an internal command or external
-command" you will need to install [java].
+
+ You don't have to manually run this command. Our npm scripts are configured so that it will be
+ automatically executed as part of the command that runs the E2E tests.
+
Since Protractor works by interacting with a running application, we need to start our web server:
@@ -261,32 +259,79 @@ Since Protractor works by interacting with a running application, we need to sta
npm start
```
-Then in a separate terminal/command line window, we can run the Protractor test scripts against the
-application by running:
+Then, in a _separate_ terminal/command line window, we can run the Protractor test scripts against
+the application by running:
```
npm run protractor
```
-Protractor will read the configuration file at `test/protractor-conf.js`. This configuration tells
-Protractor to:
+Protractor will read the configuration file at `e2e-tests/protractor.conf.js`. This configuration
+file tells Protractor to:
-- open up a Chrome browser and connect it to the application
-- execute all the End to End tests in this browser
-- report the results of these tests in the terminal/command line window
-- close down the browser and exit
+* Open up a Chrome browser and connect it to the application.
+* Execute all the E2E tests in this browser.
+* Report the results of these tests in the terminal/command line window.
+* Close the browser and exit.
-It is good to run the end to end tests whenever you make changes to the HTML views or want to check
-that the application as a whole is executing correctly. It is very common to run End to End tests
-before pushing a new commit of changes to a remote repository.
+It is good to run the E2E tests whenever you make changes to the HTML views or want to check that
+the application as a whole is executing correctly. It is very common to run E2E tests before pushing
+a new commit of changes to a remote repository.
+
+
+### Common Issues
+
+
+**Firewall / Proxy issues**
+
+Git and other tools, often use the `git:` protocol for accessing files in remote repositories.
+Some firewall configurations are blocking `git://` URLs, which leads to errors when trying to clone
+repositories or download dependencies. (For example corporate firewalls are "notorious" for blocking
+`git:`.)
+
+If you run into this issue, you can force the use of `https:` instead, by running the following
+command: `git config --global url."https://".insteadOf git://`
+
+
+**Updating WebDriver takes too long**
+
+Running `update-webdriver` for the first time may take from several seconds up to a few minutes
+(depending on your hardware and network connection). If you cancel the operation (e.g. using
+`Ctrl+C`), you might get errors, when trying to run Protractor later.
+
+In that case, you can delete the `node_modules/` directory and run `npm install` again.
+
+
+**Protractor dependencies**
+
+Under the hood, Protractor uses the [Selenium Stadalone Server][selenium], which in turn requires
+the [Java Development Kit (JDK)][jdk] to be installed on your local machine. Check this by running
+`java -version` from the command line.
+
+If JDK is not already installed, you can download it [here][jdk-download].
+
+
+**Error running the web server**
+
+The web server is configured to use port 8000. If the port is already in use (for example by another
+instance of a running web server) you will get an `EADDRINUSE` error. Make sure the port is
+available, before running `npm start`.
+
+
+
+Now that you have set up your local machine, let's get started with the tutorial:
+{@link step_00 Step 0 - Bootstrapping}
-Now that you have set up your local machine, let's get started with the tutorial: {@link step_00 Step 0 - Bootstrapping}
-[git]: http://git-scm.com/
-[node]: http://nodejs.org/
[angular-phonecat]: https://github.com/angular/angular-phonecat
-[protractor]: https://github.com/angular/protractor
[bower]: http://bower.io/
+[git]: http://git-scm.com/
[http-server]: https://github.com/nodeapps/http-server
-[karma]: https://github.com/karma-runner/karma
-[java]: https://www.java.com/en/download/help/download_options.xml
+[jdk]: https://en.wikipedia.org/wiki/Java_Development_Kit
+[jdk-download]: http://www.oracle.com/technetwork/java/javase/downloads/index.html
+[karma]: https://karma-runner.github.io/
+[node]: http://nodejs.org/
+[nvm]: https://github.com/creationix/nvm
+[nvm-windows]: https://github.com/coreybutler/nvm-windows
+[protractor]: https://github.com/angular/protractor
+[selenium]: http://docs.seleniumhq.org/
diff --git a/docs/content/tutorial/step_00.ngdoc b/docs/content/tutorial/step_00.ngdoc
index 1a83fdf5ed2a..91d8bad361f7 100644
--- a/docs/content/tutorial/step_00.ngdoc
+++ b/docs/content/tutorial/step_00.ngdoc
@@ -7,11 +7,12 @@
In this step of the tutorial, you will become familiar with the most important source code files of
-the AngularJS phonecat app. You will also learn how to start the development servers bundled with
-angular-seed, and run the application in the browser.
+the AngularJS Phonecat App. You will also learn how to start the development servers bundled with
+[angular-seed][angular-seed], and run the application in the browser.
-Before you continue, make sure you have set up your development environment and installed all necessary
-dependencies, as described in {@link index#get-started Get Started}.
+Before you continue, make sure you have set up your development environment and installed all
+necessary dependencies, as described in the {@link tutorial/#environment-setup Environment Setup}
+section.
In the `angular-phonecat` directory, run this command:
@@ -19,118 +20,130 @@ In the `angular-phonecat` directory, run this command:
git checkout -f step-0
```
-
This resets your workspace to step 0 of the tutorial app.
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.
-If you haven't already done so you need to install the dependencies by running:
+If you haven't already done so, you need to install the dependencies by running:
```
npm install
```
-To see the app running in a browser, open a *separate* terminal/command line tab or window, then
-run `npm start` to start the web server. Now, open a browser window for the app and navigate to
-`http://localhost:8000/app/`
+To see the app running in a browser, open a _separate_ terminal/command line tab or window, then run
+`npm start` to start the web server. Now, open a browser window for the app and navigate to
+http://localhost:8000/index.html.
-Note that if you already ran the master branch app prior to checking out step-0, you may see the cached
-master version of the app in your browser window at this point. Just hit refresh to re-load the page.
+Note that if you already ran the master branch app prior to checking out step-0, you may see the
+cached master version of the app in your browser window at this point. Just hit refresh to re-load
+the page.
You can now see the page in your browser. It's not very exciting, but that's OK.
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 as we progress.
-__`app/index.html`:__
+**`app/index.html`:**
```html
-
-
- My HTML File
-
-
-
-
-
-
-
Nothing here {{'yet' + '!'}}
-
-
+
+
+ My HTML File
+
+
+
+
+
+
Nothing here {{'yet' + '!'}}
+
+
```
-
## What is the code doing?
-**`ng-app` directive:**
+
+**`ng-app` attribute:**
-
+```html
+
+```
+
+The `ng-app` attribute represents an Angular directive, named `ngApp` (Angular uses `kebab-case` for
+its custom attributes and `camelCase` for the corresponding directives which implement them). This
+directive is used to flag the HTML element that 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 AngularJS application.
- The `ng-app` attribute represents an Angular directive named `ngApp` (Angular uses
- `spinal-case` for its custom attributes and `camelCase` for the corresponding directives
- which implement them).
- This directive is used to flag the html element that 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.
+For more info on `ngApp`, check out the {@link ngApp API Reference}.
-**AngularJS script tag:**
+
+**`angular.js` script tag:**
-
+```
- This code downloads the `angular.js` script which registers a callback that will be executed by the
+This code downloads the `angular.js` script which registers a callback that will be executed by the
browser when the containing HTML page is fully downloaded. When the callback is executed, Angular
-looks for the {@link ng.directive:ngApp ngApp} directive. If
-Angular finds the directive, it will bootstrap the application with the root of the application DOM
-being the element on which the `ngApp` directive was defined.
+looks for the {@link ngApp ngApp} directive. If Angular finds the directive, it will bootstrap the
+application with the root of the application DOM being the element on which the `ngApp` directive
+was defined.
+For more info on bootstrapping your app, checkout the [Bootstrap](guide/bootstrap) section of the
+Developer Guide.
+
+
**Double-curly binding with an expression:**
- Nothing here {{'yet' + '!'}}
+```html
+Nothing here {{'yet' + '!'}}
+```
This line demonstrates two core features of Angular's templating capabilities:
- * a binding, denoted by double-curlies `{{ }}`
- * a simple expression `'yet' + '!'` used in this binding.
+* A binding, denoted by double-curlies: `{{ }}`
+* A simple expression used in this binding: `'yet' + '!'`
-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.
+The binding tells Angular that it should evaluate an expression and insert the result into the DOM
+in place of the binding. As we will see in the next steps, rather than a one-time insert, a binding
+will result in efficient continuous updates whenever the result of the expression evaluation
+changes.
-{@link guide/expression Angular expression} 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 (`window`).
+{@link guide/expression Angular expressions} are JavaScript-like code snippets that are evaluated by
+Angular in the context of the current model scope, rather than within the scope of the global
+context (`window`).
-As expected, once this template is processed by Angular, the html page contains the text:
-"Nothing here yet!".
+As expected, once this template is processed by Angular, the HTML page contains the text:
-## Bootstrapping AngularJS apps
+```
+Nothing here yet!
+```
-Bootstrapping AngularJS apps automatically using the `ngApp` directive is very easy and suitable
-for most cases. In advanced cases, such as when using script loaders, you can use the
-{@link guide/bootstrap imperative / manual way} to bootstrap the app.
+## Bootstrapping Angular Applications
-There are 3 important things that happen during the app bootstrap:
+Bootstrapping Angular applications automatically using the `ngApp` directive is very easy and
+suitable for most cases. In advanced cases, such as when using script loaders, you can use the
+{@link guide/bootstrap#manual-initialization imperative/manual way} to bootstrap the application.
+
+There are 3 important things that happen during the bootstrap phase:
1. The {@link auto.$injector injector} that will be used for dependency injection is created.
-2. The injector will then create the {@link ng.$rootScope root scope} that will
- become the context for the model of our application.
+2. The injector will then create the {@link ng.$rootScope root scope} that will become the context
+ for the model of our application.
3. Angular will then "compile" the DOM starting at the `ngApp` root element, processing any
directives and bindings found along the way.
-
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.
+clicks, key presses or incoming HTTP responses) 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.
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!
@@ -140,27 +153,29 @@ and one static binding, and our model is empty. That will soon change!
## What are all these files in my working directory?
-
-Most of the files in your working directory come from the [angular-seed project][angular-seed] which
-is typically used to bootstrap new Angular projects. The seed project is pre-configured to install
-the angular framework (via `bower` into the `app/bower_components/` folder) and tools for developing
-a typical web app (via `npm`).
+Most of the files in your working directory come from the [angular-seed project][angular-seed],
+which is typically used to bootstrap new AngularJS projects. The seed project is pre-configured to
+install the AngularJS framework (via `bower` into the `app/bower_components/` directory) and tools
+for developing and testing a typical web application (via `npm`).
For the purposes of this tutorial, we modified the angular-seed with the following changes:
-* Removed the example app
-* Added phone images to `app/img/phones/`
-* Added phone data files (JSON) to `app/phones/`
+* Removed the example app.
+* Removed unused dependencies.
+* Added phone images to `app/img/phones/`.
+* Added phone data files (JSON) to `app/phones/`.
* Added a dependency on [Bootstrap](http://getbootstrap.com) in the `bower.json` file.
-
# Experiments
-* Try adding a new expression to the `index.html` that will do some math:
+
-
1 + 2 = {{ 1 + 2 }}
+* Try adding a new expression to `index.html` that will do some math:
+ ```html
+
1 + 2 = {{1 + 2}}
+ ```
# Summary
diff --git a/docs/content/tutorial/step_01.ngdoc b/docs/content/tutorial/step_01.ngdoc
index fa7c3d420604..a944a554dd80 100644
--- a/docs/content/tutorial/step_01.ngdoc
+++ b/docs/content/tutorial/step_01.ngdoc
@@ -12,11 +12,11 @@ dynamically display the same result with any set of data.
In this step you will add some basic information about two cell phones to an HTML page.
-- The page now contains a list with information about two phones.
+* The page now contains a list with information about two phones.
-
+
**`app/index.html`:**
```html
@@ -39,15 +39,19 @@ In this step you will add some basic information about two cell phones to an HTM
# Experiments
+
+
* Try adding more static HTML to `index.html`. For example:
-
Total number of phones: 2
+ ```html
+
Total number of phones: 2
+ ```
# Summary
-This addition to your app uses static HTML to display the list. Now, let's go to {@link step_02
-step 2} to learn how to use AngularJS to dynamically generate the same list.
+This addition to your app uses static HTML to display the list. Now, let's go to
+{@link step_02 step 2} to learn how to use Angular to dynamically generate the same list.
-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.
+Now, it's time to make the web page dynamic — with AngularJS. We will also add a test that verifies
+the code for the controller we are going to add.
-There are many ways to structure the code for an application. For Angular apps, we encourage the use of
-[the Model-View-Controller (MVC) design pattern](http://en.wikipedia.org/wiki/Model–View–Controller)
-to decouple the code and to separate concerns. With that in mind, let's use a little Angular and
-JavaScript to add model, view, and controller components to our app.
+There are many ways to structure the code for an application. For Angular applications, we encourage
+the use of the [Model-View-Controller (MVC) design pattern][mvc-pattern] to decouple the code and
+separate concerns. With that in mind, let's use a little Angular and JavaScript to add models,
+views, and controllers to our app.
-- The list of three phones is now generated dynamically from data
+* The list of three phones is now generated dynamically from data
## View and Template
-In Angular, the __view__ is a projection of the model through the HTML __template__. This means that
+In Angular, the **view** is a projection of the model through the HTML **template**. This means that
whenever the model changes, Angular refreshes the appropriate binding points, which updates the
view.
-The view component is constructed by Angular from this template:
+The view is constructed by Angular from this template.
-__`app/index.html`:__
+
+**`app/index.html`:**
```html
...
-
+
-
+
@@ -49,97 +50,117 @@ __`app/index.html`:__
```
-We replaced the hard-coded phone list with the {@link ng.directive:ngRepeat ngRepeat directive}
-and two {@link guide/expression Angular expressions}:
+We replaced the hard-coded phone list with the {@link ngRepeat ngRepeat} directive and two
+{@link guide/expression Angular expressions}:
-* The `ng-repeat="phone in phones"` attribute in the `
` tag is an Angular repeater directive.
-The repeater tells Angular to create a `
` element for each phone in the list using the `
`
-tag as the template.
-* The expressions wrapped in curly braces (`{{phone.name}}` and `{{phone.snippet}}`) will be replaced
-by the value of the expressions.
+* The `ng-repeat="phone in phones"` attribute on the `
` tag is an Angular repeater directive.
+ The repeater tells Angular to create a `
` element for each phone in the list, using the `
`
+ tag as the template.
+* The expressions wrapped in curly braces (`{{phone.name}}` and `{{phone.snippet}}`) will be
+ replaced by the values of the expressions.
-We have added a new directive, called `ng-controller`, which attaches a `PhoneListCtrl`
-__controller__ to the <body> tag. At this point:
+We have also added a new directive, called {@link ngController ngController}, which attaches a
+`PhoneListController` **controller** to the `` tag. At this point:
-* The expressions in curly braces (`{{phone.name}}` and `{{phone.snippet}}`) denote
-bindings, which are referring to our application model, which is set up in our `PhoneListCtrl`
-controller.
+* `PhoneListController` is in charge of the DOM sub-tree under (and including) the `` element.
+* The expressions in curly braces (`{{phone.name}}` and `{{phone.snippet}}`) denote bindings, which
+ are referring to our application model, which is set up in our `PhoneListController` controller.
-Note: We have specified an {@link angular.Module Angular Module} to load using `ng-app="phonecatApp"`,
-where `phonecatApp` is the name of our module. This module will contain the `PhoneListCtrl`.
+ Note: We have specified an {@link angular.Module Angular Module} to load using
+ `ng-app="phonecatApp"`, where `phonecatApp` is the name of our module. This module will contain
+ the `PhoneListController`.
-
## Model and Controller
-The data __model__ (a simple array of phones in object literal notation) is now instantiated within
-the `PhoneListCtrl` __controller__. The __controller__ is simply a constructor function that takes a
-`$scope` parameter:
+The data **model** (a simple array of phones in object literal notation) is now instantiated within
+the `PhoneListController` **controller**. The **controller** is simply a constructor function that
+takes a `$scope` parameter:
-__`app/js/controllers.js`:__
+
+**`app/app.js`:**
```js
-
+// Define the `phonecatApp` module
var phonecatApp = angular.module('phonecatApp', []);
-phonecatApp.controller('PhoneListCtrl', function ($scope) {
+// Define the `PhoneListController` controller on the `phonecatApp` module
+phonecatApp.controller('PhoneListController', function PhoneListController($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.'}
+ {
+ 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.'
+ }
];
});
```
-Here we declared a controller called `PhoneListCtrl` and registered it in an AngularJS
-module, `phonecatApp`. Notice that our `ng-app` directive (on the `` tag) now specifies the `phonecatApp`
-module name as the module to load when bootstrapping the Angular application.
+Here we declared a controller called `PhoneListController` and registered it in an Angular module,
+`phonecatApp`. Notice that our `ngApp` directive (on the `` tag) now specifies the
+`phonecatApp` module name as the module to load when bootstrapping the application.
Although the controller is not yet doing very much, it plays 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:
+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:
+
+* The {@link ngController ngController} directive, located on the `` tag, references the name
+ of our controller, `PhoneListController` (located in the JavaScript file `app.js`).
-* The {@link ng.directive:ngController ngController} directive, located on the `` tag,
-references the name of our controller, `PhoneListCtrl` (located in the JavaScript file
-`controllers.js`).
+* The `PhoneListController` controller attaches the phone data to the `$scope` that was injected
+ into our controller function. This _scope_ is a prototypal descendant of the _root scope_ that was
+ created when the application was defined. This controller scope is available to all bindings
+ located within the `` tag.
-* The `PhoneListCtrl` controller attaches the phone data to the `$scope` that was injected into our
-controller function. This *scope* is a prototypical descendant of the *root scope* that was created
-when the application was defined. This controller scope is available to all bindings located within
-the `` tag.
### Scope
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
+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.
To learn more about Angular scopes, see the {@link ng.$rootScope.Scope angular scope documentation}.
+
-## Tests
+
+
+ Angular scopes prototypally inherit from their parent scope, all the way up to the *root scope*
+ of the application. As a result, assigning values directly on the scope makes it easy to share
+ data across different parts of the page and create interactive applications.
+ While this approach works for prototypes and smaller applications, it quickly leads to tight
+ coupling and difficulty to reason about changes in our data model.
+
+
+ In the next step, we will learn how to better organize our code, by "packaging" related pieces
+ of application and presentation logic into isolated, reusable entities, called components.
+
+
-The "Angular way" of separating controller from the view, makes it easy to test code as it is being
-developed. If our controller is available on the global namespace then we could simply instantiate it
-with a mock `scope` object:
-__`test/e2e/scenarios.js`:__
+# Testing
+The "Angular way" of separating controller from the view, makes it easy to test code as it is being
+developed. If our controller were available on the global namespace, we could simply instantiate it
+with a mock scope object:
+
+
```js
-describe('PhoneListCtrl', function(){
+describe('PhoneListController', function() {
- it('should create "phones" model with 3 phones', function() {
- var scope = {},
- ctrl = new PhoneListCtrl(scope);
+ it('should create a `phones` model with 3 phones', function() {
+ var scope = {};
+ var ctrl = new PhoneListController(scope);
expect(scope.phones.length).toBe(3);
});
@@ -147,30 +168,31 @@ describe('PhoneListCtrl', function(){
});
```
-The test instantiates `PhoneListCtrl` and verifies that the phones array property on the scope
-contains three records. This 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.
+The test instantiates `PhoneListController` and verifies that the phones array property on the
+scope contains three records. This 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.
+
-### Testing non-Global Controllers
+## Testing non-global Controllers
-In practice, you will not want to have your controller functions in the global namespace. Instead,
-you can see that we have registered it via an anonymous constructor function on the `phonecatApp`
-module.
+In practice, you will not want to have your controller functions in the global namespace. Instead,
+you can see that we have registered it via a constructor function on the `phonecatApp` module.
In this case Angular provides a service, `$controller`, which will retrieve your controller by name.
Here is the same test using `$controller`:
-__`test/unit/controllersSpec.js`:__
+
+**`app/app.spec.js`:**
```js
-describe('PhoneListCtrl', function(){
+describe('PhoneListController', function() {
beforeEach(module('phonecatApp'));
- it('should create "phones" model with 3 phones', inject(function($controller) {
- var scope = {},
- ctrl = $controller('PhoneListCtrl', {$scope:scope});
+ it('should create a `phones` model with 3 phones', inject(function($controller) {
+ var scope = {};
+ var ctrl = $controller('PhoneListController', {$scope: scope});
expect(scope.phones.length).toBe(3);
}));
@@ -179,29 +201,46 @@ describe('PhoneListCtrl', function(){
```
* Before each test we tell Angular to load the `phonecatApp` module.
-* We ask Angular to `inject` the `$controller` service into our test function
-* We use `$controller` to create an instance of the `PhoneListCtrl`
+* We ask Angular to `inject` the `$controller` service into our test function.
+* We use `$controller` to create an instance of the `PhoneListController`.
* With this instance, we verify that the phones array property on the scope contains three records.
+
+
**A note on file naming:**
+
+ As already mentioned in the [introduction](tutorial/#running-unit-tests), the unit test files
+ (specs) are kept side-by-side with the application code. We name our specs after the file
+ containing the code to be tested plus a specific suffix to distinguish them from files
+ containing application code. Note that test files are still plain JavaScript files, so they have
+ a `.js` file extension.
+
+
+ In this tutorial, we are using the `.spec` suffix. So the test file corresponding to
+ `something.js` would be called `something.spec.js`.
+ (Another common convention is to use a `_spec` or `_test` suffix.)
+
+
+
-### Writing and Running Tests
+## Writing and Running Tests
-Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework when
-writing tests. Although Angular does not require you to use Jasmine, we wrote all of the tests in
-this tutorial in Jasmine v1.3. You can learn about Jasmine on the [Jasmine home page][jasmine] and
-at the [Jasmine docs][jasmine-docs].
+Many Angular developers prefer the syntax of
+[Jasmine's Behavior-Driven Development (BDD) framework][jasmine-home], when writing tests. Although
+Angular does not require you to use Jasmine, we wrote all of the tests in this tutorial in Jasmine
+v2.4. You can learn about Jasmine on the [Jasmine home page][jasmine-home] and at the
+[Jasmine docs][jasmine-docs].
-The angular-seed project is pre-configured to run unit tests using [Karma][karma] but you will need
+The angular-seed project is pre-configured to run unit tests using [Karma][karma], but you will need
to ensure that Karma and its necessary plugins are installed. You can do this by running
`npm install`.
-To run the tests, and then watch the files for changes: `npm test`.
+To run the tests, and then watch the files for changes execute: `npm test`
* Karma will start new instances of Chrome and Firefox browsers automatically. Just ignore them and
let them run in the background. Karma will use these browsers for test execution.
* If you only have one of the browsers installed on your machine (either Chrome or Firefox), make
- sure to update the karma configuration file before running the test. Locate the configuration file
- in `test/karma.conf.js`, then update the `browsers` property.
+ sure to update the karma configuration file (`karma.conf.js`), before running the test. Locate the
+ configuration file in the root directory and update the `browsers` property.
E.g. if you only have Chrome installed:
@@ -213,23 +252,27 @@ To run the tests, and then watch the files for changes: `npm test`.
* You should see the following or similar output in the terminal:
- info: Karma server started at http://localhost:9876/
- info (launcher): Starting browser "Chrome"
- info (Chrome 22.0): Connected on socket id tPUm9DXcLHtZTKbAEO-n
- Chrome 22.0: Executed 1 of 1 SUCCESS (0.093 secs / 0.004 secs)
+ INFO [karma]: Karma server started at http://localhost:9876/
+ INFO [launcher]: Starting browser Chrome
+ INFO [Chrome 49.0]: Connected on socket ... with id ...
+ Chrome 49.0: Executed 1 of 1 SUCCESS (0.05 secs / 0.04 secs)
Yay! The test passed! Or not...
-* To rerun the tests, just change any of the source or test .js files. Karma will notice the change
+
+* To rerun the tests, just change any of the source or test `.js` files. Karma will notice the change
and will rerun the tests for you. Now isn't that sweet?
-Make sure you don't minimize the browser that Karma opened. On some OS, memory assigned to a minimized
-browser is limited, which results in your karma tests running extremely slow.
+ Make sure you don't minimize the browser that Karma opened. On some OS, memory assigned to a
+ minimized browser is limited, which results in your karma tests running extremely slow.
+
# Experiments
+
+
* Add another binding to `index.html`. For example:
```html
@@ -238,46 +281,60 @@ browser is limited, which results in your karma tests running extremely slow.
* Create a new model property in the controller and bind to it from the template. For example:
- $scope.name = "World";
+ ```js
+ $scope.name = 'world';
+ ```
Then add a new binding to `index.html`:
-
Hello, {{name}}!
+ ```html
+
Hello, {{name}}!
+ ```
- Refresh your browser and verify that it says "Hello, World!".
+ Refresh your browser and verify that it says 'Hello, world!'.
-* Update the unit test for the controller in `./test/unit/controllersSpec.js` to reflect the previous change. For example by adding:
+* Update the unit test for the controller in `app/app.spec.js` to reflect the previous change.
+ For example by adding:
- expect(scope.name).toBe('World');
+ ```js
+ expect(scope.name).toBe('world');
+ ```
* Create a repeater in `index.html` that constructs a simple table:
-
-
row number
-
{{i}}
-
+ ```html
+
+
Row number
+
{{i}}
+
+ ```
Now, make the list 1-based by incrementing `i` by one in the binding:
-
-
row number
-
{{i+1}}
-
+ ```html
+
+
Row number
+
{{i+1}}
+
+ ```
- Extra points: try and make an 8x8 table using an additional `ng-repeat`.
+ Extra points: Try and make an 8x8 table using an additional `ng-repeat`.
-* Make the unit test fail by changing `expect(scope.phones.length).toBe(3)` to instead use `toBe(4)`.
+* Make the unit test fail by changing `expect(scope.phones.length).toBe(3)` to instead use
+ `toBe(4)`.
# Summary
-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 {@link step_03 step 3} to learn how to add full text search
-to the app.
+We now have a dynamic application which separates models, views, and controllers, and we are testing
+as we go. Let's go to {@link step_03 step 3} to learn how to improve our application's architecture,
+by utilizing components.
-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.
+In the previous step, we saw how a controller and a template worked together to convert a static
+HTML page into a dynamic view. This is a very common pattern in Single-Page Applications in general
+(and Angular applications in particular):
-* 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.
+* Instead of creating a static HTML page on the server, the client-side code "takes over" and
+ interacts dynamically with the view, updating it instantly to reflect changes in model data or
+ state, usually as a result of user interaction (we'll see an example shortly in
+ {@link step_05 step 5}).
-
+The **template** (the part of the view containing the bindings and presentation logic) acts as a
+a blueprint for how our data should be organized and presented to the user.
+The **controller** provides the context in which the bindings are evaluated and applies behavior
+and logic to our template.
+There are still a couple of areas we can do better:
-## Controller
+1. What if we want to reuse the same functionality in a different part of our application ?
+ We would need to duplicate the whole template (including the controller). This is error-prone and
+ hurts maintainability.
+2. The scope, that glues our controller and template together into a dynamic view, is not isolated
+ from other parts of the page. What this means is that a random, unrelated change in a different
+ part of the page (e.g. a property-name conflict) could have unexpected and hard-to-debug side
+ effects on our view.
-We made no changes to the controller.
+ (OK, this might not be a real concern in our minimal example, but it **is** a valid concern for
+ bigger, real-world applications.)
-## Template
+
-__`app/index.html`:__
-```html
-
-
-
-
-
- Search:
-
-
-
-
-
-
-
- {{phone.name}}
-
{{phone.snippet}}
-
-
-
-
-
-
-```
+## Components to the rescue!
-We added a standard HTML `` tag and used Angular's
-{@link ng.filter:filter filter} function to process the input for the
-{@link ng.directive:ngRepeat ngRepeat} directive.
+Since this combination (template + controller) is such a common and recurring pattern, Angular
+provides an easy and concise way to combine them together into reusable and isolated entities,
+known as _components_.
+Additionally, Angular will create a so called _isolate scope_ for each instance of our component,
+which means no prototypal inheritance and no risk of our component affecting other parts of the
+application or vice versa.
-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:
+
+
+ Since this is an introductory tutorial, we are not going to dive deep into all features provided
+ by Angular **components**. You can read more about components and their usage patterns in the
+ [Components](guide/component) section of the Developer Guide.
+
+
+ In fact, one could think of components as an opinionated and stripped-down version of their more
+ complex and verbose (but powerful) siblings, **directives**, which are Angular's way of teaching
+ HTML new tricks. You can read all about them in the [Directives](guide/directive) section of the
+ Developer Guide.
+
+
+ (**Note:** Directives are an advanced topic, so you might want to postpone studying them, until
+ you have mastered the basics.)
+
+
-* 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.
+To create a component, we use the {@link angular.Module#component .component()} method of an
+{@link module Angular module}. We must provide the name of the component and the Component
+Definition Object (CDO for short).
- In this code, the data that a user types into the input box (named __`query`__) is immediately
-available as a filter input in the list repeater (`phone in phones | filter:`__`query`__). When
-changes to the data model cause the repeater's input to change, the repeater efficiently updates
-the DOM to reflect the current state of the model.
+Remember that (since components are also directives) the name of the component is in `camelCase`,
+but we will use `kebab-case`, when referring to it in our HTML.
-
+In its simplest form, the CDO will just contain a template and a controller. (We can actually omit
+the controller and Angular will create a dummy controller for us. This is useful for simple
+"presentational" components, that don't attach any behavior to the template.)
-* Use of the `filter` filter: The {@link ng.filter:filter filter} function uses the
-`query` value to create a new array that contains only those records that match the `query`.
+Let's see an example:
- `ngRepeat` automatically updates the view in response to the changing number of phones returned
-by the `filter` filter. The process is completely transparent to the developer.
+```js
+ angular.
+ module('myApp').
+ component('greetUser', {
+ template: 'Hello, {{$ctrl.user}}!',
+ controller: function GreetUserController() {
+ this.user = 'world';
+ }
+ });
+```
-## Test
+Now, every time we include `` in our view, Angular will expand it into a
+DOM sub-tree constructed using the provided `template` and managed by an instance of the specified
+controller.
-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.
+But wait, where did that `$ctrl` come from and what does it refer to ?
-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.
+For reasons already mentioned (and for other reasons that are out of the scope of this tutorial), it
+is considered a good practice to avoid using the scope directly. We can (and should) use our
+controller instance; i.e. assign our data and methods on properties of our controller (the "`this`"
+inside the controller constructor), instead of directly to the scope.
-__`test/e2e/scenarios.js`:__
+From the template, we can refer to our controller instance using an alias. This way, the context of
+evaluation for our expressions is even more clear. By default, components use `$ctrl` as the
+controller alias, but we can override it, should the need arise.
-```js
-describe('PhoneCat App', function() {
+There are more options available, so make sure you check out the
+{@link ng.$compileProvider#component API Reference}, before using `.component()` in your own
+applications.
- describe('Phone list view', function() {
- beforeEach(function() {
- browser.get('app/index.html');
- });
+## Using Components
+Now that we know how to create components, let's refactor the HTML page to make use of our newly
+acquired skill.
- it('should filter the phone list as a user types into the search box', function() {
+
+**`app/index.html`:**
- var phoneList = element.all(by.repeater('phone in phones'));
- var query = element(by.model('query'));
+```html
+
+
+ ...
+
+
+
+
+
+
+
+
+
+
+
+```
- expect(phoneList.count()).toBe(3);
+
+**`app/app.js`:**
+
+```js
+// Define the `phonecatApp` module
+angular.module('phonecatApp', []);
+```
- query.sendKeys('nexus');
- expect(phoneList.count()).toBe(1);
+
+**`app/phone-list.component.js`:**
- query.clear();
- query.sendKeys('motorola');
- expect(phoneList.count()).toBe(2);
- });
+```js
+// Register `phoneList` component, along with its associated controller and template
+angular.
+ module('phonecatApp').
+ component('phoneList', {
+ template:
+ '
' +
+ '
' +
+ '{{phone.name}}' +
+ '
{{phone.snippet}}
' +
+ '
' +
+ '
',
+ controller: function PhoneListController() {
+ this.phones = [
+ {
+ name: 'Nexus S',
+ snippet: 'Fast just got faster with Nexus S.'
+ }, {
+ name: 'Motorola XOOM™ with Wi-Fi',
+ snippet: 'The Next, Next Generation tablet.'
+ }, {
+ name: 'MOTOROLA XOOM™',
+ snippet: 'The Next, Next Generation tablet.'
+ }
+ ];
+ }
});
-});
```
-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.
+Voilà! The resulting output should look the same, but let's see what we have gained:
-### Running End to End Tests with Protractor
-Even though the syntax of this test looks very much like our controller unit test written with
-Jasmine, the end-to-end test uses APIs of [Protractor](https://github.com/angular/protractor). Read
-about the Protractor APIs at http://angular.github.io/protractor/#/api.
+* Our phone list is reusable. Just drop `` anywhere in the page to get a
+ list of phones.
+* Our main view (`index.html`) is cleaner and more declarative. Just by looking at it, we know there
+ is a list of phones. We are not bothered with implementation details.
+* Our component is isolated and safe from "external influences". Likewise, we don't have to worry,
+ that we might accidentally break something in some other part of the application. What happens
+ inside our component, stays inside our component.
+* It's easier to test our component in isolation.
-Much like Karma is the test runner for unit tests, we use Protractor to run end-to-end tests.
-Try it with `npm run protractor`. End-to-end tests are slow, so unlike with unit tests, Protractor
-will exit after the test run and will not automatically rerun the test suite on every file change.
-To rerun the test suite, execute `npm run protractor` again.
+
- Note: You must ensure your application is being served via a web-server to test with protractor.
- You can do this using `npm start`.
- You also need to ensure you've installed the protractor and updated webdriver prior to running the
- `npm run protractor`. You can do this by issuing `npm install` and `npm run update-webdriver` into
- your terminal.
+
**A note on file naming:**
+
+ It is a good practice to distinguish different types of entities by suffix. In this tutorial, we
+ are using the `.component` suffix for components, so the definition of a `someComponent`
+ component would be in a file named `some-component.component.js`.
+
-# Experiments
+# Testing
-### Display Current Query
-Display the current value of the `query` model by adding a `{{query}}` binding into the
-`index.html` template, and see how it changes when you type in the input box.
+Although we have combined our controller with a template into a component, we still can (and should)
+unit test the controller separately, since this is where are application logic and data reside.
-### Display Query in Title
-Let's see how we can get the current value of the `query` model to appear in the HTML page title.
+In order to retrieve and instantiate a component's controller, Angular provides the
+{@link ngMock.$componentController $componentController} service.
-* Add an end-to-end test into the `describe` block, `test/e2e/scenarios.js` should look like this:
+
+ The `$controller` service that we used in the previous step, can only instantiate controllers that
+ where registered by name, using the `.controller()` method. We could have registered our component
+ controller this way too, if we wanted to. Instead, we chose to define it inline — inside the
+ CDO — to keep things localized, but either way works equally well.
+
- ```js
- describe('PhoneCat App', function() {
-
- describe('Phone list view', function() {
-
- beforeEach(function() {
- browser.get('app/index.html');
- });
-
- var phoneList = element.all(by.repeater('phone in phones'));
- var query = element(by.model('query'));
-
- it('should filter the phone list as a user types into the search box', function() {
- expect(phoneList.count()).toBe(3);
-
- query.sendKeys('nexus');
- expect(phoneList.count()).toBe(1);
-
- query.clear();
- query.sendKeys('motorola');
- expect(phoneList.count()).toBe(2);
- });
-
- it('should display the current filter value in the title bar', function() {
- query.clear();
- expect(browser.getTitle()).toMatch(/Google Phone Gallery:\s*$/);
-
- query.sendKeys('nexus');
- expect(browser.getTitle()).toMatch(/Google Phone Gallery: nexus$/);
- });
- });
- });
- ```
+
+**`app/phone-list.component.spec.js`:**
- Run protractor (`npm run protractor`) to see this test fail.
+```js
+describe('phoneList', function() {
+ // Load the module that contains the `phoneList` component before each test
+ beforeEach(module('phonecatApp'));
-* You might think you could just add the `{{query}}` to the title tag element as follows:
+ // Test the controller
+ describe('PhoneListController', function() {
- Google Phone Gallery: {{query}}
+ it('should create a `phones` model with 3 phones', inject(function($componentController) {
+ var ctrl = $componentController('phoneList');
- 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 `ng-controller="PhoneListCtrl"` directive, on the body
- element:
+ expect(ctrl.phones.length).toBe(3);
+ }));
-
+ });
- If you want to bind to the query model from the `` element, you must __move__ the
- `ngController` declaration to the HTML element because it is the common parent of both the body
- and title elements:
+});
+```
-
+The test retrieves the controller associated with the `phoneList` component, instantiates it and
+verifies that the phones array property on it contains three records. Note that the data is now on
+the controller instance itself, not on a `scope`.
- Be sure to __remove__ the `ng-controller` declaration from the body element.
-* Re-run `npm run protractor` to see the test now pass.
+## Running Tests
-* 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 {@link ng.directive:ngBind ngBind} or
-{@link ng.directive:ngBindTemplate ngBindTemplate} directives, which are invisible to the user
-while the page is loading:
+Same as before, execute `npm test` to run the tests and then watch the files for changes.
- Google Phone Gallery
+
+# Experiments
+
+
+
+* Try the experiments from the previous step, this time on the `phoneList` component.
+
+* Add a couple more phone lists on the page, by just adding more ``
+ elements in `index.html`. Now add another binding to the `phoneList` component's template:
+
+ ```js
+ template:
+ '
Total number of phones: {{$ctrl.phones.length}}
' +
+ '
' +
+ ...
+ ```
+
+ Reload the page and watch the new "feature" propagate to all phone lists. In real-world
+ applications, where the phone lists could appear on several different pages, being able to change
+ or add something in one place (e.g. a component's template) and have that change propagate
+ throughout the application, is a big win.
# Summary
-We have now added full text search and included a test to verify that search works! Now let's go on
-to {@link step_04 step 4} to learn how to add sorting capability to the phone app.
+You have learned how to organize your application and presentation logic into isolated reusable
+components. Let's go to {@link step_04 step 4} to learn how to organize our code in directories and
+files, so it remains easy to locate as our application grows.
-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.
+In this step, we will not be adding any new functionality to our application. Instead, we are going
+to take a step back, refactor our codebase and move files and code around, in order to make our
+application more easily expandable and maintainable.
-* 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.
+In the previous step, we saw how to architect our application to be modular and testable. What's
+equally important though, is organizing our codebase in a way that makes it easy (both for us and
+other developers on our team) to navigate through the code and quickly locate the pieces that are
+relevant to a specific feature or section of the application.
+
+To that end, we will explain why and how we:
+
+* Put each entity in its **own file**.
+* Organize our code by **feature area**, instead of by function.
+* Split our code into **modules** that other modules can depend on.
+
+
+ We will keep it short, not going into great detail on every good practice and convention. These
+ principles are explained in great detail in the [Angular Style Guide][styleguide], which also
+ contains many more techniques for effectively organizing Angular codebases.
+
-## Template
+## One Feature per File
-__`app/index.html`:__
+It might be tempting, for the sake of simplicity, to put everything in one file, or have one file
+per type; e.g. all controllers is one file, all components in another file, all services in a third
+file, and so on.
+This might seem to work well in the beginning, but as our application grows it becomes a burden to
+maintain. As we add more and more features, our files will get bigger and bigger and it will be
+difficult to navigate and find the code we are looking for.
-```html
- Search:
- Sort by:
-
+Instead we should put each feature/entity in its own file. Each stand-alone controller will be
+defined in its own file, each component will be defined in each own file, etc.
+Luckily, we don't need to change anything with respect to that guideline in our code, since we have
+already defined our `phoneList` component in its own `phone-list.component.js` file. Good job!
-
-
- {{phone.name}}
-
{{phone.snippet}}
-
-
-```
+We will keep this in mind though, as we add more features.
-We made the following changes to the `index.html` template:
-* First, we added a `