2011-05-11 00:45:42 +00:00
|
|
|
|
@ngdoc overview
|
2011-07-29 19:40:14 +00:00
|
|
|
|
@name Tutorial: 2 - Angular Templates
|
2011-05-02 17:16:50 +00:00
|
|
|
|
@description
|
2011-06-06 15:50:35 +00:00
|
|
|
|
|
|
|
|
|
|
<ul doc:tutorial-nav="2"></ul>
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-07-29 19:40:14 +00:00
|
|
|
|
Now it's time to make the web page dynamic -- with Angular. We'll also add a test that verifies the
|
2011-05-11 00:45:42 +00:00
|
|
|
|
code for the controller we are going to add.
|
|
|
|
|
|
|
2011-07-29 19:40:14 +00:00
|
|
|
|
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.
|
2011-05-11 00:45:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
|
<doc:tutorial-instructions step="2"></doc:tutorial-instructions>
|
2011-05-02 17:16:50 +00:00
|
|
|
|
|
2011-05-02 23:40:31 +00:00
|
|
|
|
|
2011-07-29 19:40:14 +00:00
|
|
|
|
The app now contains a list with three phones.
|
2011-05-11 00:45:42 +00:00
|
|
|
|
|
2011-05-02 17:16:50 +00:00
|
|
|
|
The most important changes are listed below. You can see the full diff on {@link
|
|
|
|
|
|
https://github.com/angular/angular-phonecat/compare/step-1...step-2 GitHub}:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Template for the View
|
|
|
|
|
|
|
2011-07-29 19:40:14 +00:00
|
|
|
|
The __view__ component is constructed by Angular from this template:
|
2011-05-11 00:45:42 +00:00
|
|
|
|
|
2011-05-02 17:16:50 +00:00
|
|
|
|
__`app/index.html`:__
|
|
|
|
|
|
<pre>
|
2012-01-07 02:10:47 +00:00
|
|
|
|
<html ng:app>
|
2011-05-02 17:16:50 +00:00
|
|
|
|
...
|
|
|
|
|
|
<body ng:controller="PhoneListCtrl">
|
|
|
|
|
|
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
<li ng:repeat="phone in phones">
|
|
|
|
|
|
{{phone.name}}
|
|
|
|
|
|
<p>{{phone.snippet}}</p>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
2012-01-07 02:10:47 +00:00
|
|
|
|
<script src="lib/angular/angular.js"></script>
|
2011-05-02 17:16:50 +00:00
|
|
|
|
<script src="js/controllers.js"></script>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
|
We replaced the hard-coded phone list with the {@link api/angular.widget.@ng:repeat ng:repeat
|
2011-07-29 19:40:14 +00:00
|
|
|
|
widget} and two {@link guide/dev_guide.expressions Angular expressions} enclosed in curly braces:
|
2011-06-06 15:50:35 +00:00
|
|
|
|
`{{phone.name}}` and `{{phone.snippet}}`:
|
2011-05-11 00:45:42 +00:00
|
|
|
|
|
2011-07-29 19:40:14 +00:00
|
|
|
|
* The `ng:repeat="phone in phones"` statement in the `<li>` tag is an Angular repeater. The
|
|
|
|
|
|
repeater tells Angular to create a `<li>` element for each phone in the list using the first `<li>`
|
|
|
|
|
|
tag as the template.
|
2011-05-11 00:45:42 +00:00
|
|
|
|
|
|
|
|
|
|
<img src="img/tutorial/tutorial_02_final.png">
|
|
|
|
|
|
|
2011-07-29 19:40:14 +00:00
|
|
|
|
* The curly braces around `phone.name` and `phone.snippet` are examples of {@link
|
|
|
|
|
|
guide/dev_guide.compiler.markup Angular markup}. The curly markup is shorthand for the Angular
|
|
|
|
|
|
directive {@link api/angular.directive.ng:bind ng:bind}. An `ng:bind` directive indicates a
|
|
|
|
|
|
template binding point to Angular. Binding points are locations in a template where Angular creates
|
|
|
|
|
|
data-binding between the view and the model.
|
|
|
|
|
|
|
|
|
|
|
|
In Angular, the view is a projection of the model through the HTML template. This means that
|
|
|
|
|
|
whenever the model changes, Angular refreshes the appropriate binding points, which updates the
|
|
|
|
|
|
view.
|
2011-05-11 00:45:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
2011-05-02 17:16:50 +00:00
|
|
|
|
## Model and Controller
|
|
|
|
|
|
|
2011-05-11 00:45:42 +00:00
|
|
|
|
The data __model__ (a simple array of phones in object literal notation) is instantiated within
|
2011-10-07 18:27:49 +00:00
|
|
|
|
the __controller__ function(`PhoneListCtrl`):
|
2011-05-11 00:45:42 +00:00
|
|
|
|
|
2011-05-02 17:16:50 +00:00
|
|
|
|
__`app/js/controllers.js`:__
|
|
|
|
|
|
<pre>
|
|
|
|
|
|
function PhoneListCtrl() {
|
|
|
|
|
|
this.phones = [{"name": "Nexus S",
|
|
|
|
|
|
"snippet": "Fast just got faster with Nexus S."},
|
|
|
|
|
|
{"name": "Motorola XOOM™ with Wi-Fi",
|
|
|
|
|
|
"snippet": "The Next, Next Generation tablet."},
|
|
|
|
|
|
{"name": "MOTOROLA XOOM™",
|
|
|
|
|
|
"snippet": "The Next, Next Generation tablet."}];
|
|
|
|
|
|
}
|
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
2011-05-11 00:45:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-05-02 17:16:50 +00:00
|
|
|
|
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
|
2011-07-29 19:40:14 +00:00
|
|
|
|
the model and the view. We connected the dots between the presentation, data, and logic components
|
|
|
|
|
|
as follows:
|
2011-05-11 00:45:42 +00:00
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
* The name of our controller function(in the JavaScript file `controllers.js`) matches the {@link
|
2011-06-07 05:02:30 +00:00
|
|
|
|
api/angular.directive.ng:controller ng:controller} directive in the `<body>` tag (`PhoneListCtrl`).
|
2011-07-29 19:40:14 +00:00
|
|
|
|
* The data is instantiated within the *scope* of our controller function; our template binding
|
2011-06-07 05:02:30 +00:00
|
|
|
|
points are located within the block bounded by the `<body ng:controller="PhoneListCtrl">` tag.
|
2011-05-11 00:45:42 +00:00
|
|
|
|
|
2011-07-29 19:40:14 +00:00
|
|
|
|
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.
|
|
|
|
|
|
|
2011-11-12 01:15:22 +00:00
|
|
|
|
To learn more about Angular scopes, see the {@link api/angular.module.ng.$rootScope.Scope angular scope documentation}.
|
2011-05-11 00:45:42 +00:00
|
|
|
|
|
2011-05-02 17:16:50 +00:00
|
|
|
|
|
|
|
|
|
|
## Tests
|
|
|
|
|
|
|
2011-07-29 19:40:14 +00:00
|
|
|
|
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:
|
2011-05-02 17:16:50 +00:00
|
|
|
|
|
|
|
|
|
|
__`test/unit/controllersSpec.js`:__
|
|
|
|
|
|
<pre>
|
|
|
|
|
|
describe('PhoneCat controllers', function() {
|
|
|
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
|
describe('PhoneListCtrl', function() {
|
2011-05-02 17:16:50 +00:00
|
|
|
|
|
|
|
|
|
|
it('should create "phones" model with 3 phones', function() {
|
|
|
|
|
|
var ctrl = new PhoneListCtrl();
|
|
|
|
|
|
expect(ctrl.phones.length).toBe(3);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
2011-07-29 19:40:14 +00:00
|
|
|
|
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.
|
2011-05-02 17:16:50 +00:00
|
|
|
|
|
2011-05-11 00:45:42 +00:00
|
|
|
|
Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework when
|
2011-07-29 19:40:14 +00:00
|
|
|
|
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
|
|
|
|
|
|
http://pivotal.github.com/jasmine/ Jasmine home page} and on the {@link
|
|
|
|
|
|
https://github.com/pivotal/jasmine/wiki Jasmine wiki}.
|
2011-05-11 00:45:42 +00:00
|
|
|
|
|
2011-05-02 23:40:31 +00:00
|
|
|
|
The angular-seed project is pre-configured to run all unit tests using {@link
|
2011-05-02 17:16:50 +00:00
|
|
|
|
http://code.google.com/p/js-test-driver/ JsTestDriver}. To run the test, do the following:
|
|
|
|
|
|
|
2011-05-02 23:40:31 +00:00
|
|
|
|
1. In a _separate_ terminal window or tab, go to the `angular-phonecat` directory and run
|
|
|
|
|
|
`./scripts/test-server.sh` to start the test web server.
|
|
|
|
|
|
|
2011-05-11 00:45:42 +00:00
|
|
|
|
2. Open a new browser tab or window and navigate to {@link http://localhost:9876}.
|
|
|
|
|
|
|
|
|
|
|
|
3. Choose "Capture this browser in strict mode".
|
|
|
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
|
At this point, you can leave this tab open and forget about it. JsTestDriver will use it to
|
|
|
|
|
|
execute the tests and report the results in the terminal.
|
2011-05-11 00:45:42 +00:00
|
|
|
|
|
|
|
|
|
|
4. Execute the test by running `./scripts/test.sh`
|
|
|
|
|
|
|
2011-05-02 23:40:31 +00:00
|
|
|
|
You should see the following or similar output:
|
|
|
|
|
|
|
|
|
|
|
|
Chrome: Runner reset.
|
|
|
|
|
|
.
|
|
|
|
|
|
Total 1 tests (Passed: 1; Fails: 0; Errors: 0) (2.00 ms)
|
|
|
|
|
|
Chrome 11.0.696.57 Mac OS: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (2.00 ms)
|
|
|
|
|
|
|
2011-05-11 00:45:42 +00:00
|
|
|
|
Yay! The test passed! Or not...
|
|
|
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
|
Note: If you see errors after you run the test, close the browser tab and go back to the terminal
|
2011-05-11 00:45:42 +00:00
|
|
|
|
and kill the script, then repeat the procedure above.
|
|
|
|
|
|
|
2011-05-02 23:40:31 +00:00
|
|
|
|
# Experiments
|
|
|
|
|
|
|
2011-05-11 00:45:42 +00:00
|
|
|
|
* Add another binding to `index.html`. For example:
|
|
|
|
|
|
|
2011-05-02 23:40:31 +00:00
|
|
|
|
<p>Total number of phones: {{phones.length}}</p>
|
|
|
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
|
* Create a new model property in the controller and bind to it from the template. For example:
|
2011-05-02 23:40:31 +00:00
|
|
|
|
|
2011-05-11 00:45:42 +00:00
|
|
|
|
this.hello = "Hello, World!"
|
|
|
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
|
Refresh your browser to make sure it says, "Hello, World!"
|
2011-05-11 00:45:42 +00:00
|
|
|
|
|
2011-05-02 23:40:31 +00:00
|
|
|
|
* Create a repeater that constructs a simple table:
|
|
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
|
|
Now, make the list 1-based by incrementing `i` by one in the binding:
|
2011-05-02 17:16:50 +00:00
|
|
|
|
|
2011-05-02 23:40:31 +00:00
|
|
|
|
<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>
|
2011-05-02 17:16:50 +00:00
|
|
|
|
|
2011-05-02 23:40:31 +00:00
|
|
|
|
* Make the unit test fail by changing the `toBe(3)` statement to `toBe(4)`, and rerun the
|
|
|
|
|
|
`./scripts/test.sh` script.
|
2011-05-02 17:16:50 +00:00
|
|
|
|
|
|
|
|
|
|
|
2011-05-02 23:40:31 +00:00
|
|
|
|
# Summary
|
2011-05-02 17:16:50 +00:00
|
|
|
|
|
2011-07-29 19:40:14 +00:00
|
|
|
|
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.
|
2011-05-02 17:16:50 +00:00
|
|
|
|
|
|
|
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
|
<ul doc:tutorial-nav="2"></ul>
|