-
-The following sample code is our starting point. It is a static HTML page that displays next to
-nothing, but it has everything we need to proceed. You can think of this bit of code as our
-prototype template, consisting of basic HTML tags with a pair of angular specific attributes.
-
-__`app/index.html`:__
-
-
-## Discussion:
-
-Although our app doesn't appear to do anything dynamic, note the following:
-
-* __... `xmlns:ng="http://angularjs.org"` ...__ This `xmlns` declaration for the `ng` namespace
-must be specified if you use XHTML, or if you are targeting IE older than 9 (regardless of whether
-you are using XHTML or HTML).
-
-* __`
-
- This will download the angular script from the angular server instead of from a local file.
-
-* To try this code out in your browser, you need to navigate to the step-0 page (you are currently
-on Step 0 of the tutorial). If your http server is running, navigate to `app/index.html`.
-Remember, this is a relative URL (see the Relative URL section in {@link tutorial Tutorial}). The
-browser will display the same thing as you would see if you go to
-http://angular.github.com/angular-phonecat/step-0/app (accessible from Example link at the bottom
-of the page).
-
-Now we can move on and add some content to our developing web app.
-
-
+
+The following sample code is our starting point. It is a static HTML page that displays next to
+nothing, but it has everything we need to proceed. You can think of this bit of code as our
+prototype template, consisting of basic HTML tags with a pair of angular specific attributes.
+
+__`app/index.html`:__
+
+
+## Discussion:
+
+Although our app doesn't appear to do anything dynamic, note the following:
+
+* __... `xmlns:ng="http://angularjs.org"` ...__ This `xmlns` declaration for the `ng` namespace
+must be specified if you use XHTML, or if you are targeting IE older than 9 (regardless of whether
+you are using XHTML or HTML).
+
+* __`
+
+ This will download the angular script from the angular server instead of from a local file.
+
+* To try this code out in your browser, you need to navigate to the step-0 page (you are currently
+on Step 0 of the tutorial). If your http server is running, navigate to `app/index.html`.
+Remember, this is a relative URL (see the Relative URL section in {@link tutorial Tutorial}). The
+browser will display the same thing as you would see if you go to
+http://angular.github.com/angular-phonecat/step-0/app (accessible from Example link at the bottom
+of the page).
+
+Now we can move on and add some content to our developing web app.
+
+
+
+Now that we have the basic ingredients in place, let's add some basic information about two cell
+phones to our app.
+
+Note: We will usually include only the new code that we added for each step. In this and
+subsequent examples, we will leave out code from the previous step that hasn't changed, for
+example:
+
+ ...
+
+ ...
+
+Let's add the following code to `index.html`:
+
+__`app/index.html`:__
+
+
+...
+ Google Phone Gallery
+...
+
+...
+
+
+ Nexus S
+
+ Fast just got faster with Nexus S.
+
+
+
+ Motorola XOOM™ with Wi-Fi
+
+ The Next, Next Generation tablet.
+
+
+
+...
+
+
+## Discussion:
+
+* It's a static web page! We displayed info about two phones! Yay.
+
+* For those of you playing along at home on your own web servers, did you switch to Step 1 and
+refresh your browsers?
+
+ * __{@link tutorial Using Git:}__
+
+ From your `angular-phonecat` directory, run this command:
+
+ git checkout step-1
+
+ * __{@link tutorial Using Snapshots:}__
+
+ From `[install directory]/sandbox`, run this command:
+
+ ./goto_step.sh 1
+
+* Now would be a good time to open up `app/index.html` in your browser and see the current state
+of our "application". It's not very exciting, but that's ok.
+
+When you're ready, let's move on and start using some angular features to turn this static page
+into a dynamic web app.
+
+
+
+In the last step, we remembered what a basic, static web page looks like, and now we want to get
+dynamic. There are many ways to do this, but an important feature of angular is the incorporation
+of the principles behind {@link http://en.wikipedia.org/wiki/Model–View–Controller the MVC design
+pattern} into client-side web apps. With that in mind, let's use a little angular and a little
+JavaScript to add Model, View, and Controller components to our app, and change the static page
+into one that is dynamically generated.
+
+Our __View__ component is constructed by angular from this template:
+
+__`app/index.html`:__
+
+...
+
+
+
+
+ {{phone.name}}
+
{{phone.snippet}}
+
+
+
+
+
+
+...
+
+
+Our data __Model__ (a short list of phones in object literal notation) is instantiated within our
+__Controller__ function (`PhoneListCtrl`):
+
+__`app/js/controllers.js`:__
+
+/* App Controllers */
+
+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."}];
+}
+
+
+The "Angular way" urges us to test as we develop:
+
+__`test/unit/controllersSpec.js`:__
+
+/* jasmine specs for controllers go here */
+describe('PhoneCat controllers', function() {
+
+ describe('PhoneListCtrl', function(){
+
+ it('should create "phones" model with 3 phones', function() {
+ var ctrl = new PhoneListCtrl();
+ expect(ctrl.phones.length).toBe(3);
+ });
+ });
+});
+
+
+## Discussion:
+
+So what were our changes from Step 1?
+
+* __View template:__ We replaced the hard-coded phone list with the {@link
+angular.widget.@ng:repeat ng:repeat widget} and two {@link guide.expression angular expressions}
+enclosed in curly braces: `{{phone.name}}` and `{{phone.snippet}}`:
+
+ * The `ng:repeat="phone in phones"` statement in the `
` tag is an angular repeater. It
+ tells angular to create a `
` element for each phone in the phones list, using the first
+ `
` tag as the template.
+
+ * The curly braces around `phone.name` and `phone.snippet` are an example of {@link
+ angular.markup angular markup}. The curly braces are shorthand for the angular directive
+ {@link angular.directive.ng:bind ng:bind}. They indicate to angular that these are template
+ binding points. Binding points are locations in the 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.
+
+* __Controller:__ At this point, it doesn't appear as if our controller is doing very much
+controlling, but it is playing a crucial role: providing context for our data model so we can
+establish data-binding between the model and the view. Note in the following how we connected the
+dots between our presentation, data, and logic components:
+
+ * The name of our controller function (in the JavaScript file `controllers.js`) matches the
+ {@link angular.directive.ng:controller ng:controller} directive in the `` tag
+ (`PhoneListCtrl`).
+ * We instantiated our data within the scope of our controller function, and our template
+ binding points are located within the block bounded by the `
+
+
+We did a lot of work in laying the foundation of our app in the last step, so now we'll do
+something simple, and add full text search. 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.
+
+__`app/index.html`:__
+
+...
+ Fulltext Search:
+
+
+
+ {{phone.name}}
+
{{phone.snippet}}
+
+
+...
+
+__`test/e2e/scenarios.js`:__
+
+/* jasmine-like end2end tests go here */
+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);
+ });
+ });
+});
+
+
+## Discussion:
+
+We continued using the same controller that we set up in Step 2, but we added the following
+features to our app:
+
+* __Search Box:__ A standard HTML `` tag combined with angular's {@link
+angular.Array.filter $filter} utility (added to the repeater) lets a user type in search criteria
+and immediately see the effects of their search on the phone list. This new code demonstrates the
+following:
+
+ * Two way 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.
+
+In this example, the data that you type into the input box (named __`query`__) is immediately
+available as a filter input in the list repeater (`phone in phones.$filter(`__`query`__`)`).
+Whenever the data model changes and this change causes the input to the repeater to change, the
+repeater will efficiently update the DOM to reflect the current state of the model.
+
+ * Use of `$filter` in a template. The `$filter` function is one of several built-in {@link
+ angular.Array angular functions} that augment JavaScript arrays during their evaluation as
+ angular expressions. In {@link guide.expression angular expressions}, these array utilities are
+ available as array methods. (They are prefixed with a $ to avoid naming collisions.)
+
+ * `ng:repeat` automatically shrinks and grows the number of phones in the View, via DOM
+ manipulation that is completely transparent to the developer. If you've written any DOM
+ manipulation code, this should make you happy.
+
+* __CSS:__ We added in some minimal CSS to the file we set up in Step 0: `./css/app.css`.
+
+* __Testing:__ To run the end to end test, open http://localhost:8000/test/e2e/runner.html in
+your browser. This end-to-end test shows the following:
+
+ * Proof that the search box and the repeater are correctly wired together.
+
+ * How easy it is to write end-to-end tests. This is just a simple test, but the point here is
+ to show how easy it is to set up a functional, readable, end-to-end test.
+
+
+
+In this step, we add a feature that lets our users choose which way to order the phone list.
+
+__`app/index.html`:__
+
+...
+
+
+ Search:
+
+
+ Sort by:
+
+
+
+
+
+
+ {{phone.name}}
+
{{phone.snippet}}
+
+
+...
+
+
+__`app/js/controller.js`:__
+
+/* App Controllers */
+
+function PhoneListCtrl() {
+ this.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}];
+
+ this.orderProp = 'age';
+}
+
+
+__`test/unit/controllerSpec.js`:__
+
+/* jasmine specs for controllers go here */
+describe('PhoneCat controllers', function() {
+
+ describe('PhoneListCtrl', function(){
+ var scope, $browser, ctrl;
+
+ beforeEach(function() {
+ ctrl = new PhoneListCtrl();
+ });
+
+
+ it('should create "phones" model with 3 phones', function() {
+ expect(ctrl.phones.length).toBe(3);
+ });
+
+
+ it('should set the default value of orderProp model', function() {
+ expect(ctrl.orderProp).toBe('age');
+ });
+ });
+});
+
+
+__`test/e2e/scenarios.js`:__
+
+/* jasmine-like end2end tests go here */
+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);
+ });
+
+
+ it('should be possible to control phone order via the drop down select box', function() {
+ input('query').enter('tablet'); //let's narrow the dataset to make the test assertions
+ shorter
+
+ expect(repeater('.phones li', 'Phone List').column('a')).
+ toEqual(["Motorola XOOM\u2122 with Wi-Fi",
+ "MOTOROLA XOOM\u2122"]);
+
+ select('orderProp').option('alphabetical');
+
+ expect(repeater('.phones li', 'Phone List').column('a')).
+ toEqual(["MOTOROLA XOOM\u2122",
+ "Motorola XOOM\u2122 with Wi-Fi"]);
+ });
+ });
+});
+
+
+## Discussion:
+
+To provide dynamic ordering, we employ another one of angular's "array type augmenters" and let
+the data binding do the rest of the work for us:
+
+* First, we provide a `
+
+## Discussion:
+
+* Phone Details View Template. There is nothing fancy or new here, just note where we use the
+angular `{{ expression }}` markup and directives to project phone data from our model into the
+view.
+
+* Note how we used the `$route` `params` object from the scope managed by the root controller
+(`PhoneCatCtrl`), to construct the path for the phone details xhr request. The rest of this step
+is simply applying the previously learned concepts and angular APIs to create a large template
+that displays a lot of data about a phone.
+
+* Tests. We updated the existing end to end test and wrote a new unit test that is similar in
+spirit to the one we wrote for the `PhoneListCtrl` controller.
+
+
+
+
{@link tutorial.step_00 Previous}
+
{@link http://angular.github.com/angular-phonecat/step-8/app Live Demo
+}
+
+In this step, we have determined that the built-in angular display filters ({@link
+angular.filter.number number}, {@link angular.filter.currency currency}, {@link
+angular.filter.date date}, etc.) don't handle what we want to do, so we get to create our own
+custom {@link angular.filter filter}.
+
+In the previous step, the details page displayed either "true" or "false" to indicate whether
+certain phone features were present or not. Our custom "checkmark" filter replaces those text
+strings with glyphs: ✓ for "true", and ✘ for "false".
+
+Our filter code lives in `app/js/filters.js`:
+
+__`app/index.html`:__
+
+...
+
+
+
+...
+
+
+In the phone details template, we employ our filter for angular expressions whose values are
+"true" or "false"; `{{ [phone_feature] | checkmark }}`:
+
+__`app/partials/phone-detail.html`:__
+
+
+## Discussion:
+
+* This example shows how easy it is to roll your own filters for displaying data. As explained in
+the "Writing your own Filters" section of the {@link angular.filter angular.filter} page, you
+simply register your custom filter function on to the `angular.filter` function.
+
+* In this example, our filter name is "checkmark"; our input is either "true" or "false", and we
+return one of two unicode characters we have chosen to represent true or false (`\u2713` and
+`\u2718`).
+
+* We created a new unit test to verify that our custom filter converts boolean values to unicode
+characters.
+
+
+
+
{@link tutorial.step_00 Previous}
+
{@link http://angular.github.com/angular-phonecat/step-9/app Live Demo
+}
-
-Now that we have the basic ingredients in place, let's add some basic information about two cell
-phones to our app.
-
-Note: We will usually include only the new code that we added for each step. In this and
-subsequent examples, we will leave out code from the previous step that hasn't changed, for
-example:
-
- ...
-
- ...
-
-Let's add the following code to `index.html`:
-
-__`app/index.html`:__
-
-
-...
- Google Phone Gallery
-...
-
-...
-
-
- Nexus S
-
- Fast just got faster with Nexus S.
-
-
-
- Motorola XOOM™ with Wi-Fi
-
- The Next, Next Generation tablet.
-
-
-
-...
-
-
-## Discussion:
-
-* It's a static web page! We displayed info about two phones! Yay.
-
-* For those of you playing along at home on your own web servers, did you switch to Step 1 and
-refresh your browsers?
-
- * __{@link tutorial Using Git:}__
-
- From your `angular-phonecat` directory, run this command:
-
- git checkout step-1
-
- * __{@link tutorial Using Snapshots:}__
-
- From `[install directory]/sandbox`, run this command:
-
- ./goto_step.sh 1
-
-* Now would be a good time to open up `app/index.html` in your browser and see the current state
-of our "application". It's not very exciting, but that's ok.
-
-When you're ready, let's move on and start using some angular features to turn this static page
-into a dynamic web app.
-
-
-
-In the last step, we remembered what a basic, static web page looks like, and now we want to get
-dynamic. There are many ways to do this, but an important feature of angular is the incorporation
-of the principles behind {@link http://en.wikipedia.org/wiki/Model–View–Controller the MVC design
-pattern} into client-side web apps. With that in mind, let's use a little angular and a little
-JavaScript to add Model, View, and Controller components to our app, and change the static page
-into one that is dynamically generated.
-
-Our __View__ component is constructed by angular from this template:
-
-__`app/index.html`:__
-
-...
-
-
-
-
- {{phone.name}}
-
{{phone.snippet}}
-
-
-
-
-
-
-...
-
-
-Our data __Model__ (a short list of phones in object literal notation) is instantiated within our
-__Controller__ function (`PhoneListCtrl`):
-
-__`app/js/controllers.js`:__
-
-/* App Controllers */
-
-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."}];
-}
-
-
-The "Angular way" urges us to test as we develop:
-
-__`test/unit/controllersSpec.js`:__
-
-/* jasmine specs for controllers go here */
-describe('PhoneCat controllers', function() {
-
- describe('PhoneListCtrl', function(){
-
- it('should create "phones" model with 3 phones', function() {
- var ctrl = new PhoneListCtrl();
- expect(ctrl.phones.length).toBe(3);
- });
- });
-});
-
-
-## Discussion:
-
-So what were our changes from Step 1?
-
-* __View template:__ We replaced the hard-coded phone list with the {@link
-angular.widget.@ng:repeat ng:repeat widget} and two {@link guide.expression angular expressions}
-enclosed in curly braces: `{{phone.name}}` and `{{phone.snippet}}`:
-
- * The `ng:repeat="phone in phones"` statement in the `
` tag is an angular repeater. It
- tells angular to create a `
` element for each phone in the phones list, using the first
- `
` tag as the template.
-
- * The curly braces around `phone.name` and `phone.snippet` are an example of {@link
- angular.markup angular markup}. The curly braces are shorthand for the angular directive
- {@link angular.directive.ng:bind ng:bind}. They indicate to angular that these are template
- binding points. Binding points are locations in the 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.
-
-* __Controller:__ At this point, it doesn't appear as if our controller is doing very much
-controlling, but it is playing a crucial role: providing context for our data model so we can
-establish data-binding between the model and the view. Note in the following how we connected the
-dots between our presentation, data, and logic components:
-
- * The name of our controller function (in the JavaScript file `controllers.js`) matches the
- {@link angular.directive.ng:controller ng:controller} directive in the `` tag
- (`PhoneListCtrl`).
- * We instantiated our data within the scope of our controller function, and our template
- binding points are located within the block bounded by the `
-
-
-We did a lot of work in laying the foundation of our app in the last step, so now we'll do
-something simple, and add full text search. 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.
-
-__`app/index.html`:__
-
-...
- Fulltext Search:
-
-
-
- {{phone.name}}
-
{{phone.snippet}}
-
-
-...
-
-__`test/e2e/scenarios.js`:__
-
-/* jasmine-like end2end tests go here */
-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);
- });
- });
-});
-
-
-## Discussion:
-
-We continued using the same controller that we set up in Step 2, but we added the following
-features to our app:
-
-* __Search Box:__ A standard HTML `` tag combined with angular's {@link
-angular.Array.filter $filter} utility (added to the repeater) lets a user type in search criteria
-and immediately see the effects of their search on the phone list. This new code demonstrates the
-following:
-
- * Two way 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.
-
-In this example, the data that you type into the input box (named __`query`__) is immediately
-available as a filter input in the list repeater (`phone in phones.$filter(`__`query`__`)`).
-Whenever the data model changes and this change causes the input to the repeater to change, the
-repeater will efficiently update the DOM to reflect the current state of the model.
-
- * Use of `$filter` in a template. The `$filter` function is one of several built-in {@link
- angular.Array angular functions} that augment JavaScript arrays during their evaluation as
- angular expressions. In {@link guide.expression angular expressions}, these array utilities are
- available as array methods. (They are prefixed with a $ to avoid naming collisions.)
-
- * `ng:repeat` automatically shrinks and grows the number of phones in the View, via DOM
- manipulation that is completely transparent to the developer. If you've written any DOM
- manipulation code, this should make you happy.
-
-* __CSS:__ We added in some minimal CSS to the file we set up in Step 0: `./css/app.css`.
-
-* __Testing:__ To run the end to end test, open http://localhost:8000/test/e2e/runner.html in
-your browser. This end-to-end test shows the following:
-
- * Proof that the search box and the repeater are correctly wired together.
-
- * How easy it is to write end-to-end tests. This is just a simple test, but the point here is
- to show how easy it is to set up a functional, readable, end-to-end test.
-
-
-
-In this step, we add a feature that lets our users choose which way to order the phone list.
-
-__`app/index.html`:__
-
-...
-
-
- Search:
-
-
- Sort by:
-
-
-
-
-
-
- {{phone.name}}
-
{{phone.snippet}}
-
-
-...
-
-
-__`app/js/controller.js`:__
-
-/* App Controllers */
-
-function PhoneListCtrl() {
- this.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}];
-
- this.orderProp = 'age';
-}
-
-
-__`test/unit/controllerSpec.js`:__
-
-/* jasmine specs for controllers go here */
-describe('PhoneCat controllers', function() {
-
- describe('PhoneListCtrl', function(){
- var scope, $browser, ctrl;
-
- beforeEach(function() {
- ctrl = new PhoneListCtrl();
- });
-
-
- it('should create "phones" model with 3 phones', function() {
- expect(ctrl.phones.length).toBe(3);
- });
-
-
- it('should set the default value of orderProp model', function() {
- expect(ctrl.orderProp).toBe('age');
- });
- });
-});
-
-
-__`test/e2e/scenarios.js`:__
-
-/* jasmine-like end2end tests go here */
-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);
- });
-
-
- it('should be possible to control phone order via the drop down select box', function() {
- input('query').enter('tablet'); //let's narrow the dataset to make the test assertions
- shorter
-
- expect(repeater('.phones li', 'Phone List').column('a')).
- toEqual(["Motorola XOOM\u2122 with Wi-Fi",
- "MOTOROLA XOOM\u2122"]);
-
- select('orderProp').option('alphabetical');
-
- expect(repeater('.phones li', 'Phone List').column('a')).
- toEqual(["MOTOROLA XOOM\u2122",
- "Motorola XOOM\u2122 with Wi-Fi"]);
- });
- });
-});
-
-
-## Discussion:
-
-To provide dynamic ordering, we employ another one of angular's "array type augmenters" and let
-the data binding do the rest of the work for us:
-
-* First, we provide a `