2012-03-30 21:02:26 +00:00
|
|
|
|
@ngdoc overview
|
|
|
|
|
|
@name Tutorial: 2 - Angular Templates
|
|
|
|
|
|
@description
|
|
|
|
|
|
|
2012-04-29 05:45:28 +00:00
|
|
|
|
<ul doc-tutorial-nav="2"></ul>
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
2012-04-02 15:32:30 +00:00
|
|
|
|
Now it's time to make the web page dynamic — with AngularJS. We'll also add a test that verifies the
|
2012-03-30 21:02:26 +00:00
|
|
|
|
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 {@link http://en.wikipedia.org/wiki/Model–View–Controller the Model-View-Controller (MVC)
|
|
|
|
|
|
design pattern} 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
2012-04-29 05:45:28 +00:00
|
|
|
|
<div doc-tutorial-reset="2"></div>
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The app now contains a list with three phones.
|
|
|
|
|
|
|
|
|
|
|
|
The most important changes are listed below. You can see the full diff on {@link
|
|
|
|
|
|
https://github.com/angular/angular-phonecat/compare/step-1...step-2 GitHub}:
|
|
|
|
|
|
|
|
|
|
|
|
|
2012-04-02 15:32:30 +00:00
|
|
|
|
## View and Template
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
2012-04-02 15:32:30 +00:00
|
|
|
|
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:
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
|
|
|
|
|
__`app/index.html`:__
|
|
|
|
|
|
<pre>
|
2013-10-03 10:58:47 +00:00
|
|
|
|
<html ng-app="phonecatApp">
|
2012-04-02 15:32:30 +00:00
|
|
|
|
<head>
|
|
|
|
|
|
...
|
|
|
|
|
|
<script src="lib/angular/angular.js"></script>
|
|
|
|
|
|
<script src="js/controllers.js"></script>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body ng-controller="PhoneListCtrl">
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
|
|
|
|
|
<ul>
|
2012-04-02 15:32:30 +00:00
|
|
|
|
<li ng-repeat="phone in phones">
|
2012-03-30 21:02:26 +00:00
|
|
|
|
{{phone.name}}
|
|
|
|
|
|
<p>{{phone.snippet}}</p>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
</ul>
|
2013-10-03 10:58:47 +00:00
|
|
|
|
|
2012-03-30 21:02:26 +00:00
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
2012-04-02 15:32:30 +00:00
|
|
|
|
We replaced the hard-coded phone list with the
|
2012-06-12 06:49:24 +00:00
|
|
|
|
{@link api/ng.directive:ngRepeat ngRepeat directive} and two
|
2012-05-24 21:49:47 +00:00
|
|
|
|
{@link guide/expression Angular expressions} enclosed in curly braces:
|
2012-03-30 21:02:26 +00:00
|
|
|
|
`{{phone.name}}` and `{{phone.snippet}}`:
|
|
|
|
|
|
|
2012-04-02 15:32:30 +00:00
|
|
|
|
* The `ng-repeat="phone in phones"` statement in the `<li>` tag is an Angular repeater. The
|
2012-03-30 21:02:26 +00:00
|
|
|
|
repeater tells Angular to create a `<li>` element for each phone in the list using the first `<li>`
|
|
|
|
|
|
tag as the template.
|
|
|
|
|
|
|
2013-10-03 10:58:47 +00:00
|
|
|
|
We have added a new directive, called `ng-controller`, which attaches a `PhoneListCtrl`
|
|
|
|
|
|
__controller__ to the DOM at this point.
|
|
|
|
|
|
|
|
|
|
|
|
* As we've learned in {@link step_00 step 0}, the curly braces around `phone.name` and `phone.snippet` denote
|
2012-09-05 14:28:42 +00:00
|
|
|
|
bindings. As opposed to evaluating constants, these expressions are referring to our application
|
2012-04-02 15:32:30 +00:00
|
|
|
|
model, which was set up in our `PhoneListCtrl` controller.
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
2013-08-20 02:42:26 +00:00
|
|
|
|
<img class="diagram" src="img/tutorial/tutorial_02.png">
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
|
|
|
|
|
## Model and Controller
|
|
|
|
|
|
|
2013-10-03 10:58:47 +00:00
|
|
|
|
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:
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
|
|
|
|
|
__`app/js/controllers.js`:__
|
|
|
|
|
|
<pre>
|
2013-10-03 09:16:44 +00:00
|
|
|
|
|
2013-10-08 17:45:00 +00:00
|
|
|
|
var phonecatApp = angular.module('phonecatApp', []);
|
|
|
|
|
|
|
2013-10-14 21:25:00 +00:00
|
|
|
|
phonecatApp.controller('PhoneListCtrl', function ($scope) {
|
2012-04-27 22:18:54 +00:00
|
|
|
|
$scope.phones = [
|
2013-10-08 17:45:00 +00:00
|
|
|
|
{'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.'}
|
2012-04-27 22:18:54 +00:00
|
|
|
|
];
|
2013-10-08 17:45:00 +00:00
|
|
|
|
});
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
2013-10-03 10:58:47 +00:00
|
|
|
|
</pre>
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
2013-10-11 22:55:53 +00:00
|
|
|
|
Here we declared a controller called `PhoneListCtrl` and registered it in an AngularJS
|
2013-10-03 10:58:47 +00:00
|
|
|
|
module, `phonecatApp`. Notice that our `ng-app` directive (on the `<html>` tag) now specifies the `phonecatApp`
|
|
|
|
|
|
module name as the module to load when bootstrapping the Angular application.
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
2013-10-08 17:45:00 +00:00
|
|
|
|
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
|
2012-03-30 21:02:26 +00:00
|
|
|
|
the model and the view. We connected the dots between the presentation, data, and logic components
|
|
|
|
|
|
as follows:
|
|
|
|
|
|
|
2013-10-03 10:58:47 +00:00
|
|
|
|
* The {@link api/ng.directive:ngController ngController} directive, located on the `<body>` tag,
|
2013-11-09 10:49:13 +00:00
|
|
|
|
references the name of our controller, `PhoneListCtrl` (located in the JavaScript file
|
2013-10-03 10:58:47 +00:00
|
|
|
|
`controllers.js`).
|
2012-04-02 15:32:30 +00:00
|
|
|
|
|
2013-10-03 10:58:47 +00:00
|
|
|
|
* 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
|
2012-04-02 15:32:30 +00:00
|
|
|
|
the `<body ng-controller="PhoneListCtrl">` tag.
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
2013-10-03 10:58:47 +00:00
|
|
|
|
### Scope
|
|
|
|
|
|
|
|
|
|
|
|
The concept of a scope in Angular is crucial. A scope can be seen as the glue which allows the
|
2012-03-30 21:02:26 +00:00
|
|
|
|
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.
|
|
|
|
|
|
|
2013-10-03 10:58:47 +00:00
|
|
|
|
To learn more about Angular scopes, see the {@link api/ng.$rootScope.Scope angular scope documentation}.
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Tests
|
|
|
|
|
|
|
2013-10-03 10:58:47 +00:00
|
|
|
|
The "Angular way" of separating controller from the view, makes it easy to test code as it is being
|
|
|
|
|
|
developed. If our controller is available on the global namespace then we can simply instantiate it
|
|
|
|
|
|
with a mock `scope` object. Take a look at the following unit test for our controller:
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
|
|
|
|
|
__`test/unit/controllersSpec.js`:__
|
|
|
|
|
|
<pre>
|
|
|
|
|
|
describe('PhoneCat controllers', function() {
|
|
|
|
|
|
|
2012-04-27 22:18:54 +00:00
|
|
|
|
describe('PhoneListCtrl', function(){
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
|
|
|
|
|
it('should create "phones" model with 3 phones', function() {
|
2012-04-02 15:32:30 +00:00
|
|
|
|
var scope = {},
|
|
|
|
|
|
ctrl = new PhoneListCtrl(scope);
|
|
|
|
|
|
|
|
|
|
|
|
expect(scope.phones.length).toBe(3);
|
2012-03-30 21:02:26 +00:00
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
2013-10-03 10:58:47 +00:00
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
### Testing non-Global Controllers
|
2013-10-17 14:05:28 +00:00
|
|
|
|
In practice, you will not want to have your controller functions in the global namespace. Instead,
|
|
|
|
|
|
we have registered our controllers in the `phonecatApp` module. In this case Angular provides a
|
|
|
|
|
|
service, `$controller`, which will retrieve your controller by name. Here is the same test using
|
|
|
|
|
|
`$controller`:
|
2013-10-03 10:58:47 +00:00
|
|
|
|
|
|
|
|
|
|
__`test/unit/controllersSpec.js`:__
|
|
|
|
|
|
<pre>
|
|
|
|
|
|
describe('PhoneCat controllers', function() {
|
2013-10-17 14:05:28 +00:00
|
|
|
|
beforeEach(module('phonecatApp'));
|
2013-10-03 10:58:47 +00:00
|
|
|
|
|
|
|
|
|
|
describe('PhoneListCtrl', function(){
|
|
|
|
|
|
|
|
|
|
|
|
it('should create "phones" model with 3 phones', inject(function($controller) {
|
|
|
|
|
|
var scope = {},
|
|
|
|
|
|
ctrl = $controller('PhoneListCtrl', { $scope: scope });
|
|
|
|
|
|
|
|
|
|
|
|
expect(scope.phones.length).toBe(3);
|
2013-10-06 20:18:26 +00:00
|
|
|
|
}));
|
2013-10-03 10:58:47 +00:00
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
</pre>
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
2013-10-17 14:05:28 +00:00
|
|
|
|
Don't forget that we need to load up the `phonecatApp` module into the test so that the controller
|
|
|
|
|
|
is available to be injected.
|
|
|
|
|
|
|
2013-10-03 10:58:47 +00:00
|
|
|
|
### Writing and Running Tests
|
2012-03-30 21:02:26 +00:00
|
|
|
|
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 {@link
|
2013-11-19 14:43:27 +00:00
|
|
|
|
http://pivotal.github.com/jasmine/ Jasmine home page} and at the {@link
|
|
|
|
|
|
http://pivotal.github.io/jasmine/ Jasmine docs}.
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
|
|
|
|
|
The angular-seed project is pre-configured to run all unit tests using {@link
|
2013-12-05 22:06:28 +00:00
|
|
|
|
http://karma-runner.github.io/ Karma}. Ensure that the necessary karma plugins are installed.
|
|
|
|
|
|
You can do this by issuing `npm install` into your terminal.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
To run the test, do the following:
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
|
|
|
|
|
1. In a _separate_ terminal window or tab, go to the `angular-phonecat` directory and run
|
2013-04-15 11:28:31 +00:00
|
|
|
|
`./scripts/test.sh` to start the Karma server (the config file necessary to start the server
|
2013-05-17 19:53:38 +00:00
|
|
|
|
is located at `./config/karma.conf.js`).
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
2013-04-15 11:28:31 +00:00
|
|
|
|
2. Karma will start a new instance of Chrome browser automatically. Just ignore it and let it run in
|
|
|
|
|
|
the background. Karma will use this browser for test execution.
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
2012-10-18 09:33:45 +00:00
|
|
|
|
3. You should see the following or similar output in the terminal:
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
2013-04-15 11:28:31 +00:00
|
|
|
|
info: Karma server started at http://localhost:9876/
|
2012-10-18 09:33:45 +00:00
|
|
|
|
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)
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
|
|
|
|
|
Yay! The test passed! Or not...
|
|
|
|
|
|
|
2013-04-15 11:28:31 +00:00
|
|
|
|
4. To rerun the tests, just change any of the source or test files. Karma will notice the change
|
2012-10-18 09:33:45 +00:00
|
|
|
|
and will rerun the tests for you. Now isn't that sweet?
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
|
|
|
|
|
# Experiments
|
|
|
|
|
|
|
|
|
|
|
|
* Add another binding to `index.html`. For example:
|
|
|
|
|
|
|
|
|
|
|
|
<p>Total number of phones: {{phones.length}}</p>
|
|
|
|
|
|
|
|
|
|
|
|
* Create a new model property in the controller and bind to it from the template. For example:
|
|
|
|
|
|
|
2012-04-10 13:04:13 +00:00
|
|
|
|
$scope.hello = "Hello, World!"
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
|
|
|
|
|
Refresh your browser to make sure it says, "Hello, World!"
|
|
|
|
|
|
|
|
|
|
|
|
* Create a repeater that constructs a simple table:
|
|
|
|
|
|
|
|
|
|
|
|
<table>
|
|
|
|
|
|
<tr><th>row number</th></tr>
|
2012-04-02 15:32:30 +00:00
|
|
|
|
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i}}</td></tr>
|
2012-03-30 21:02:26 +00:00
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
|
|
Now, make the list 1-based by incrementing `i` by one in the binding:
|
|
|
|
|
|
|
|
|
|
|
|
<table>
|
|
|
|
|
|
<tr><th>row number</th></tr>
|
2012-04-02 15:32:30 +00:00
|
|
|
|
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
|
2012-03-30 21:02:26 +00:00
|
|
|
|
</table>
|
|
|
|
|
|
|
2012-10-18 09:33:45 +00:00
|
|
|
|
* Make the unit test fail by changing the `toBe(3)` statement to `toBe(4)`.
|
2012-03-30 21:02:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
2012-04-29 05:45:28 +00:00
|
|
|
|
<ul doc-tutorial-nav="2"></ul>
|