From 6336b6e89e3a80aec3c4367ab4c2737fd365c030 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Fri, 30 Mar 2012 14:02:26 -0700 Subject: chore(docs): restore old tutorial ngdoc files --- docs/content/tutorial/index.ngdoc | 150 +++++++++++++++++++++++++ docs/content/tutorial/step_00.ngdoc | 216 ++++++++++++++++++++++++++++++++++++ docs/content/tutorial/step_01.ngdoc | 57 ++++++++++ docs/content/tutorial/step_02.ngdoc | 203 +++++++++++++++++++++++++++++++++ docs/content/tutorial/step_03.ngdoc | 181 ++++++++++++++++++++++++++++++ docs/content/tutorial/step_04.ngdoc | 198 +++++++++++++++++++++++++++++++++ docs/content/tutorial/step_05.ngdoc | 216 ++++++++++++++++++++++++++++++++++++ docs/content/tutorial/step_06.ngdoc | 105 ++++++++++++++++++ docs/content/tutorial/step_07.ngdoc | 210 +++++++++++++++++++++++++++++++++++ docs/content/tutorial/step_08.ngdoc | 186 +++++++++++++++++++++++++++++++ docs/content/tutorial/step_09.ngdoc | 121 ++++++++++++++++++++ docs/content/tutorial/step_10.ngdoc | 140 +++++++++++++++++++++++ docs/content/tutorial/step_11.ngdoc | 208 ++++++++++++++++++++++++++++++++++ docs/content/tutorial/the_end.ngdoc | 21 ++++ 14 files changed, 2212 insertions(+) create mode 100644 docs/content/tutorial/index.ngdoc create mode 100644 docs/content/tutorial/step_00.ngdoc create mode 100644 docs/content/tutorial/step_01.ngdoc create mode 100644 docs/content/tutorial/step_02.ngdoc create mode 100644 docs/content/tutorial/step_03.ngdoc create mode 100644 docs/content/tutorial/step_04.ngdoc create mode 100644 docs/content/tutorial/step_05.ngdoc create mode 100644 docs/content/tutorial/step_06.ngdoc create mode 100644 docs/content/tutorial/step_07.ngdoc create mode 100644 docs/content/tutorial/step_08.ngdoc create mode 100644 docs/content/tutorial/step_09.ngdoc create mode 100644 docs/content/tutorial/step_10.ngdoc create mode 100644 docs/content/tutorial/step_11.ngdoc create mode 100644 docs/content/tutorial/the_end.ngdoc (limited to 'docs/content/tutorial') diff --git a/docs/content/tutorial/index.ngdoc b/docs/content/tutorial/index.ngdoc new file mode 100644 index 00000000..c26ea2df --- /dev/null +++ b/docs/content/tutorial/index.ngdoc @@ -0,0 +1,150 @@ +@ngdoc overview +@name Tutorial +@description + +A great way to get introduced to Angular is to work through this tutorial, which walks you through +the construction of an AngularJS web app. The app you will build is a catalog that displays a list +of Android devices, lets you filter the list to see only devices that interest you, and then view +details for any device. + + + +Work through the tutorial to see how Angular makes browsers smarter — without the use of extensions +or plug-ins. As you work through the tutorial, you will: + +* See examples of how to use client-side data binding and dependency injection to build dynamic +views of data that change immediately in response to user actions. +* See how Angular creates listeners on your data without the need for DOM manipulation. +* Learn a better, easier way to test your web apps. +* Learn how to use Angular services to make common web tasks, such as getting data into your app, +easier. + +And all of this works in any browser without modification to the browser! + +When you finish the tutorial you will be able to: + +* Create a dynamic application that works in any browser. +* Define the differences between Angular and common JavaScript frameworks. +* Understand how data binding works in AngularJS. +* Use the angular-seed project to quickly boot-strap your own projects. +* Create and run tests. +* Identify resources for learning more about AngularJS. + +The tutorial guides you through the entire process of building a simple application, including +writing and running unit and end-to-end tests. Experiments at the end of each step provide +suggestions for you learn more about AngularJS and the application you are building. + +You can go through the whole tutorial in a couple of hours or you may want to spend a pleasant day +really digging into it. If you're looking for a shorter introduction to AngularJS, check out the +{@link misc/started Getting Started} document. + + + + + + + +# Working with the code + +You can follow this tutorial and hack on the code in either the Mac/Linux or the Windows +environment. Options for working with the tutorial are to use the Git versioning system for source +code management or to use scripts that copy snapshots of project files into your workspace +(`sandbox`) directory. Select one of the tabs below and follow the instructions for setting up your +computer for your preferred option. + + + +
    +
  1. Verify that you have Java installed by running the +following command in a terminal window:

    +
    java -version
    +

    You will need Java to run unit tests.

  2. +
  3. Download Git from the Git site.

    +

    You can build Git from source or use the pre-compiled package.

  4. +
  5. Clone the angular-phonecat repository located at Github by running the following command:

    +
    git clone git://github.com/angular/angular-phonecat.git
    +

    This command creates the angular-phonecat directory in your current +directory.

  6. +
  7. Change your current directory to angular-phonecat:

    +
    cd angular-phonecat
    +

    The tutorial instructions assume you are running all commands from the angular-phonecat +directory.

  8. +
  9. You will need an http server running on your system. Mac and Linux machines typically +have Apache pre-installed, but If you don't already have one installed, you can install +node.js. Use node to run scripts/web-server.js, a simple bundled +http server.

  10. +
+
+ + +
    +
  1. You will need Java to run unit tests, so run the following command to verify that you +have Java installed and that the java executable is on +your PATH.

    +
    java -version
    +

  2. +
  3. Install msysGit from the Git site.

  4. +
  5. Open msysGit bash and clone the angular-phonecat repository located at Github by running the following command:

    +
    git clone git://github.com/angular/angular-phonecat.git
    +

    This command creates the angular-phonecat directory in your current directory.

  6. +
  7. Change your current directory to angular-phonecat.

    +
    cd angular-phonecat
    +

    The tutorial instructions assume you are running all commands from the angular-phonecat +directory.

    +

    You should run all git commands from msysGit bash.

    +

    Other commands like test-server.bat or test.bat should be +executed from the Windows command line.

  8. +
  9. You need an http server running on your system. If you don't already have one +installed, you can install node.js. Download the pre-compiled binaries, unzip them, and then add +nodejs\bin into your PATH. Use node to run +scripts\web-server.js, a simple, bundled http server.

  10. +
+
+ + +
    +
  1. You need Java to run unit tests, so verify that you have Java installed by running the following command in a terminal +window:

    +
    java -version
    +
  2. Download the zip archive +containing all of the files and unzip them into the [tutorial-dir] directory

    .
  3. +
  4. Change your current directory to [tutorial-dir]/sandbox, as follows:

    +
    cd [tutorial-dir]/sandbox
    +

    The tutorial instructions assume you are running all commands from your +sandbox directory.

  5. +
  6. You need an http server running on your system and Mac and Linux machines typically +have Apache pre-installed. If you don't have an http server installed, you can install +node.js and use it to run scripts/web-server.js, a simple bundled http +server.

  7. +
+
+ + +
    +
  1. Verify that you have Java installed and that the +java executable is on your PATH by running the following command in the +Windows command line:

    +
    java -version
    +

    You need Java to run unit tests, so download the zip archive that contains all of the files +and unzip the files into the [tutorial-dir] directory

  2. +
  3. Change your current directory to [tutorial-dir]/sandbox, as follows:

    +
    cd [tutorial-dir]/sandbox
    +

    The tutorial instructions assume you are running all commands from this directory.

  4. +
  5. You need an http server running on your system, but if you don't already have one +already installed, you can install node.js. Download the pre-compiled binaries, unzip them, and then add +nodejs\bin into your PATH. Use node to run +scripts\web-server.js, a simple bundled http server.

  6. +
+
+
+ +The last thing to do is to make sure your computer has a web browser and a good text editor +installed. Now, let's get going with {@link step_00 step 0}. diff --git a/docs/content/tutorial/step_00.ngdoc b/docs/content/tutorial/step_00.ngdoc new file mode 100644 index 00000000..b7f469ff --- /dev/null +++ b/docs/content/tutorial/step_00.ngdoc @@ -0,0 +1,216 @@ +@ngdoc overview +@name Tutorial: 0 - angular-seed +@description + + + + +You are now ready to build the Angular phonecat application. In this step, you will become familiar +with the most important source code files, learn how to start the development servers bundled with +angular-seed, and run the application in the browser. + + + + +
    +
  1. In 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.

  2. + +
  3. To see the app running in a browser, do one of the following: +
      +
    • For node.js users: +
        +
      1. In a separate terminal tab or window, run +./scripts/web-server.js to start the web server.
      2. +
      3. Open a browser window for the app and navigate to http://localhost:8000/app/index.html
      4. +
      +
    • +
    • For other http servers: +
        +
      1. Configure the server to serve the files in the angular-phonecat +directory.
      2. +
      3. Navigate in your browser to +http://localhost:[port-number]/[context-path]/app/index.html.
      4. +
      +
    • +
    +
  4. +
+
+ + + +
    +
  1. Open msysGit bash and run this command (in angular-phonecat directory):

    +
    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.

  2. +
  3. To see the app running in a browser, do one of the following: +
      +
    • For node.js users: +
        +
      1. In a separate terminal tab or window, run node +scripts\web-server.js to start the web server.
      2. +
      3. Open a browser window for the app and navigate to http://localhost:8000/app/index.html
      4. +
      +
    • +
    • For other http servers: +
        +
      1. Configure the server to serve the files in the angular-phonecat +directory.
      2. +
      3. Navigate in your browser to +http://localhost:[port-number]/[context-path]/app/index.html.
      4. +
      +
    • +
    +
  4. +
+
+ + + +
    +
  1. In the angular-phonecat directory, run this command:

    +
    ./goto_step.sh 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.

  2. +
  3. To see the app running in a browser, do one of the following: +
      +
    • For node.js users: +
        +
      1. In a separate terminal tab or window, run +./scripts/web-server.js to start the web server.
      2. +
      3. Open a browser window for the app and navigate to http://localhost:8000/app/index.html
      4. +
      +
    • +
    • For other http servers: +
        +
      1. Configure the server to serve the files in the angular-phonecat +sandbox directory.
      2. +
      3. Navigate in your browser to +http://localhost:[port-number]/[context-path]/app/index.html.
      4. +
      +
    • +
    +
  4. +
+
+ + + +
    +
  1. Open windows command line and run this command (in the angular-phonecat directory):

    +
    goto_step.bat 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.

  2. +
  3. To see the app running in a browser, do one of the following: +
      +
    • For node.js users: +
        +
      1. In a separate terminal tab or window, run node +scripts\web-server.js to start the web server.
      2. +
      3. Open a browser window for the app and navigate to http://localhost:8000/app/index.html
      4. +
      +
    • +
    • For other http servers: +
        +
      1. Configure the server to serve the files in the angular-phonecat +sandbox directory.
      2. +
      3. Navigate in your browser to +http://localhost:[port-number]/[context-path]/app/index.html.
      4. +
      +
    • +
    +
  4. +
+
+
+ + +You can now see the page in your browser. It's not very exciting, but that's OK. + +The static HTML page that displays "Nothing here yet!" was constructed with the HTML code shown +below. The code contains some key Angular elements that we will need going forward. + +__`app/index.html`:__ +
+
+
+
+  
+  my angular app
+  
+
+
+
+  Nothing here yet!
+
+  
+
+
+
+ + + +## What is the code doing? + +* xmlns declaration + + + + This `xmlns` declaration for the `ng` namespace must be specified in all Angular applications in +order to make Angular work with XHTML and IE versions older than 9 (regardless of whether you are +using XHTML or HTML). + +* Angular script tag + + + + + + + +We replaced the hard-coded phone list with the {@link api/angular.widget.@ng:repeat ng:repeat +widget} and two {@link guide/dev_guide.expressions 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. The +repeater tells Angular to create a `
  • ` element for each phone in the list using the first `
  • ` +tag as the template. + + + +* 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. + + +## Model and Controller + +The data __model__ (a simple array of phones in object literal notation) is instantiated within +the __controller__ function(`PhoneListCtrl`): + +__`app/js/controllers.js`:__ +
    +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."}];
    +}
    +
    + + + + +Although the controller is not yet doing very much controlling, it is playing a crucial role. By +providing context for our data model, the controller allows us to establish data-binding between +the model and the view. We connected the dots between the presentation, data, and logic components +as follows: + +* The name of our controller function(in the JavaScript file `controllers.js`) matches the {@link +api/angular.directive.ng:controller ng:controller} directive in the `` tag (`PhoneListCtrl`). +* The data is instantiated within the *scope* of our controller function; our template binding +points are located within the block bounded by the `` tag. + + 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. + + To learn more about Angular scopes, see the {@link api/angular.module.ng.$rootScope.Scope angular scope documentation}. + + +## Tests + +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: + +__`test/unit/controllersSpec.js`:__ +
    +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);
    +    });
    +  });
    +});
    +
    + +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. + +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 +http://pivotal.github.com/jasmine/ Jasmine home page} and on the {@link +https://github.com/pivotal/jasmine/wiki Jasmine wiki}. + +The angular-seed project is pre-configured to run all unit tests using {@link +http://code.google.com/p/js-test-driver/ JsTestDriver}. To run the test, do the following: + +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. + +2. Open a new browser tab or window and navigate to {@link http://localhost:9876}. + +3. Choose "Capture this browser in strict mode". + + 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. + +4. Execute the test by running `./scripts/test.sh` + + 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) + + Yay! The test passed! Or not... + + Note: If you see errors after you run the test, close the browser tab and go back to the terminal +and kill the script, then repeat the procedure above. + +# Experiments + +* Add another binding to `index.html`. For example: + +

    Total number of phones: {{phones.length}}

    + +* Create a new model property in the controller and bind to it from the template. For example: + + this.hello = "Hello, World!" + + Refresh your browser to make sure it says, "Hello, World!" + +* Create a repeater that constructs a simple table: + + + + +
    row number
    {{i}}
    + + Now, make the list 1-based by incrementing `i` by one in the binding: + + + + +
    row number
    {{i+1}}
    + +* Make the unit test fail by changing the `toBe(3)` statement to `toBe(4)`, and rerun the +`./scripts/test.sh` script. + + +# 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. + + + diff --git a/docs/content/tutorial/step_03.ngdoc b/docs/content/tutorial/step_03.ngdoc new file mode 100644 index 00000000..fef4743f --- /dev/null +++ b/docs/content/tutorial/step_03.ngdoc @@ -0,0 +1,181 @@ +@ngdoc overview +@name Tutorial: 3 - Filtering Repeaters +@description + + + + +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. + + + + + +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. + +The most important differences between Steps 2 and 3 are listed below. You can see the full diff on +{@link https://github.com/angular/angular-phonecat/compare/step-2...step-3 + GitHub}: + + +## Controller + +We made no changes to the controller. + + +## Template + +__`app/index.html`:__ +
    +...
    +   Fulltext Search: 
    +
    +  
    +...
    +
    + +We added a standard HTML `` tag and used angular's {@link api/angular.module.ng.$filter.filter $filter} +function to process the input for the `ng:repeater`. + +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: + +* 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 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. + + + +* Use of `$filter`. The {@link api/angular.module.ng.$filter.filter $filter} method uses the `query` value to +create a new array that contains only those records that match the `query`. + + `ng:repeat` automatically updates the view in response to the changing number of phones returned +by the `$filter`. The process is completely transparent to the developer. + +## Test + +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. + +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. + +__`test/e2e/scenarios.js`:__ +
    +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);
    +    });
    +  });
    +});
    +
    + +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 {@link guide/dev_guide.e2e-testing Angular's end-to-end +test runner}. + +To run the end-to-end test, open one of the following in a new browser tab: + +* node.js users: {@link http://localhost:8000/test/e2e/runner.html} +* users with other http servers: +`http://localhost:[port-number]/[context-path]/test/e2e/runner.html` +* casual reader: {@link http://angular.github.com/angular-phonecat/step-3/test/e2e/runner.html} + +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. + +# Experiments + +* 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. + +* Let's see how we can get the current value of the `query` model to appear in the HTML page title. + + You might think you could just add the {{query}} to the title tag element as follows: + + Google Phone Gallery: {{query}} + + However, when you reload the page, you won't see the expected result. This is because the "query" +model lives in the scope defined by the body element: + + + + If you want to bind to the query model from the `` element, you must __move__ the +`ng:controller` declaration to the HTML element because it is the common parent of both the body +and title elements: + + <html ng:controller="PhoneListCtrl"> + + Be sure to *remove* the `ng:controller` declaration from the body element. + + While using double curlies works fine in 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 api/angular.directive.ng:bind ng:bind} or {@link +api/angular.directive.ng:bind-template ng:bind-template} directives, which are invisible to the +user while the page is loading: + + <title ng:bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery + +* Add the following end-to-end test into the `describe` block within `test/e2e/scenarios.js`: + +
    +    it('should display the current filter value within an element with id "status"',
    +        function() {
    +      expect(element('#status').text()).toMatch(/Current filter: \s*$/);
    +
    +      input('query').enter('nexus');
    +
    +      expect(element('#status').text()).toMatch(/Current filter: nexus\s*$/);
    +
    +      //alternative version of the last assertion that tests just the value of the binding
    +      using('#status').expect(binding('query')).toBe('nexus');
    +    });
    +  
    + + Refresh the browser tab with the end-to-end test runner to see the test fail. To make the test +pass, edit the `index.html` template to add a `div` or `p` element with `id` `"status"` and content +with the `query` binding. + +* Add a `pause()` statement into an end-to-end test and rerun it. You'll see the runner pause; this +gives you the opportunity to explore the state of your application while it is displayed in the +browser. The app is live! You can change the search query to prove it. Notice how useful this is +for troubleshooting end-to-end tests. + + +# 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. + + + + diff --git a/docs/content/tutorial/step_04.ngdoc b/docs/content/tutorial/step_04.ngdoc new file mode 100644 index 00000000..a5fefd74 --- /dev/null +++ b/docs/content/tutorial/step_04.ngdoc @@ -0,0 +1,198 @@ +@ngdoc overview +@name Tutorial: 4 - Two-way Data Binding +@description + + + + +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. + + + + + +You should see that in addition to the search box, the app displays a drop down menu that allows +users to control the order in which the phones are listed. + +The most important differences between Steps 3 and 4 are listed below. You can see the full diff on +{@link https://github.com/angular/angular-phonecat/compare/step-3...step-4 GitHub}: + + +## Template + +__`app/index.html`:__ +
    +...
    +  
    +
    +  
    +...
    +
    + +We made the following changes to the `index.html` template: + +* First, we added a ` +
  • +
  • + Sort by: + +
  • + + + + + + + +We also added a placeholder template for the phone details view: + +__`app/partials/phone-detail.html`:__ +
    +TBD: detail view for {{params.phoneId}}
    +
    + +Note how we are using `params` model defined in the `PhoneCatCtrl` controller. + + +## Test + +To automatically verify that everything is wired properly, we wrote end-to-end tests that navigate +to various URLs and verify that the correct view was rendered. + +
    +...
    +  it('should redirect index.html to index.html#/phones', function() {
    +   browser().navigateTo('../../app/index.html');
    +   expect(browser().location().hash()).toBe('/phones');
    +  });
    +...
    +
    + describe('Phone detail view', function() {
    +
    +   beforeEach(function() {
    +      browser().navigateTo('../../app/index.html#/phones/nexus-s');
    +   });
    +
    +
    +   it('should display placeholder page with phoneId', function() {
    +      expect(binding('params.phoneId')).toBe('nexus-s');
    +   });
    + });
    +
    + + +You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you +can see them running on {@link +http://angular.github.com/angular-phonecat/step-7/test/e2e/runner.html +angular's server}. + + +# Experiments + +* Try to add an `{{orderProp}}` binding to `index.html`, and you'll see that nothing happens even +when you are in the phone list view. This is because the `orderProp` model is visible only in the +scope managed by `PhoneListCtrl`, which is associated with the `` element. If you add the +same binding into the `phone-list.html` template, the binding will work as expected. + +* In `PhoneCatCtrl`, create a new model called "`hero`" with `this.hero = 'Zoro'`. In +`PhoneListCtrl` let's shadow it with `this.hero = 'Batman'`, and in `PhoneDetailCtrl` we'll use +`this.hero = "Captain Proton"`. Then add the `

    hero = {{hero}}

    ` to all three of our templates +(`index.html`, `phone-list.html`, and `phone-detail.html`). Open the app and you'll see scope +inheritance and model property shadowing do some wonders. + +# Summary + +With the routing set up and the phone list view implemented, we're ready to go to {@link step_08 +step 8} to implement the phone details view. + + + diff --git a/docs/content/tutorial/step_08.ngdoc b/docs/content/tutorial/step_08.ngdoc new file mode 100644 index 00000000..a81c689b --- /dev/null +++ b/docs/content/tutorial/step_08.ngdoc @@ -0,0 +1,186 @@ +@ngdoc overview +@name Tutorial: 8 - More Templating +@description + + + + +In this step, you will implement the phone details view, which is displayed when a user clicks on a +phone in the phone list. + + + + + +Now when you click on a phone on the list, the phone details page with phone-specific information +is displayed. + +To implement the phone details view we will use {@link api/angular.module.ng.$xhr $xhr} to fetch our +data, and we'll flesh out the `phone-details.html` view template. + +The most important changes are listed below. You can see the full diff on {@link +https://github.com/angular/angular-phonecat/compare/step-7...step-8 +GitHub}: + +## Data + +In addition to `phones.json`, the `app/phones/` directory also contains one json file for each +phone: + +__`app/phones/nexus-s.json`:__ (sample snippet) +
    +{
    +  "additionalFeatures": "Contour Display, Near Field Communications (NFC),...",
    +  "android": {
    +      "os": "Android 2.3",
    +      "ui": "Android"
    +  },
    +  ...
    +  "images": [
    +      "img/phones/nexus-s.0.jpg",
    +      "img/phones/nexus-s.1.jpg",
    +      "img/phones/nexus-s.2.jpg",
    +      "img/phones/nexus-s.3.jpg"
    +  ],
    +  "storage": {
    +      "flash": "16384MB",
    +      "ram": "512MB"
    +  }
    +}
    +
    + + +Each of these files describes various properties of the phone using the same data structure. We'll +show this data in the phone detail view. + + +## Controller + +We'll expand the `PhoneDetailCtrl` by using the `$xhr` service to fetch the json files. This works +the same way as the phone list controller. + +__`app/js/controller.js`:__ +
    +function PhoneDetailCtrl($xhr) {
    +  var self = this;
    +
    +  $xhr('GET', 'phones/' + self.params.phoneId + '.json', function(code, response) {
    +    self.phone = response;
    +  });
    +}
    +
    +//PhoneDetailCtrl.$inject = ['$xhr'];
    +
    + +To construct the URL for the HTTP request, we use `params.phoneId` extracted from the current route +in the `PhoneCatCtrl` controller. + + +## Template + +The TBD placeholder line has been replaced with lists and bindings that comprise the phone details. +Note where we use the angular `{{expression}}` markup and `ng:repeater`s to project phone data from +our model into the view. + + +__`app/partials/phone-details.html`:__ +
    +
    +
    +

    {{phone.name}}

    + +

    {{phone.description}}

    + + + + +
    + + + +## Test + +We wrote a new unit test that is similar to the one we wrote for the `PhoneListCtrl` controller in +step 5. + +__`test/unit/controllerSpec.js`:__ +
    +...
    +    it('should fetch phone detail', function() {
    +      scope.params = {phoneId:'xyz'};
    +      $browser.xhr.expectGET('phones/xyz.json').respond({name:'phone xyz'});
    +      ctrl = scope.$new(PhoneDetailCtrl);
    +
    +      expect(ctrl.phone).toBeUndefined();
    +      $browser.xhr.flush();
    +
    +      expect(ctrl.phone).toEqual({name:'phone xyz'});
    +    });
    +...
    +
    + +To run the unit tests, execute the `./scripts/test.sh` script and you should see the following +output. + + Chrome: Runner reset. + ... + Total 3 tests (Passed: 3; Fails: 0; Errors: 0) (5.00 ms) + Chrome 11.0.696.57 Mac OS: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (5.00 ms) + + +We also added a new end-to-end test that navigates to the Nexus S detail page and verifies that the +heading on the page is "Nexus S". + +__`test/e2e/scenarios.js`:__ +
    +...
    +  describe('Phone detail view', function() {
    +
    +    beforeEach(function() {
    +      browser().navigateTo('../../app/index.html#/phones/nexus-s');
    +    });
    +
    +
    +    it('should display nexus-s page', function() {
    +      expect(binding('phone.name')).toBe('Nexus S');
    +    });
    +  });
    +...
    +
    + + +You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you +can see them running on {@link +http://angular.github.com/angular-phonecat/step-8/test/e2e/runner.html +angular's server}. + +# Experiments + +* Using the {@link guide/dev_guide.e2e-testing Angular's end-to-end test runner API}, write a test +that verifies that we display 4 thumbnail images on the Nexus S details page. + + +# Summary + +Now that the phone details view is in place, proceed to {@link step_09 step 9} to learn how to +write your own custom display filter. + + + diff --git a/docs/content/tutorial/step_09.ngdoc b/docs/content/tutorial/step_09.ngdoc new file mode 100644 index 00000000..c0df9e1f --- /dev/null +++ b/docs/content/tutorial/step_09.ngdoc @@ -0,0 +1,121 @@ +@ngdoc overview +@name Tutorial: 9 - Filters +@description + + + + +In this step you will learn how to create your own custom display filter. + + + + + +Navigate to one of the detail pages. + +In the previous step, the details page displayed either "true" or "false" to indicate whether +certain phone features were present or not. We have used a custom filter to convert those text +strings into glyphs: ✓ for "true", and ✘ for "false". Let's see, what the filter code looks like. + +The most important changes are listed below. You can see the full diff on {@link +https://github.com/angular/angular-phonecat/compare/step-8...step-9 +GitHub}: + + +## Custom Filter + +In order to create a new filter, simply register your custom filter function with the {@link +api/angular.module.ng.$filter `angular.module.ng.$filter`} API. + +__`app/js/filters.js`:__ +
    +angular.module.ng.$filter('checkmark', function(input) {
    +  return input ? '\u2713' : '\u2718';
    +});
    +
    + +The name of our filter is "checkmark". The `input` evaluates to either `true` or `false`, and we +return one of two unicode characters we have chosen to represent true or false (`\u2713` and +`\u2718`). + + +## Template + +Since the filter code lives in the `app/js/filters.js` file, we need to include this file in our +layout template. + +__`app/index.html`:__ +
    +...
    + 
    + 
    +...
    +
    + +The syntax for using filters in angular templates is as follows: + + {{ expression | filter }} + +Let's employ the filter in the phone details template: + + + +__`app/partials/phone-detail.html`:__ +
    +...
    +    
    +
    Infrared
    +
    {{phone.connectivity.infrared | checkmark}}
    +
    GPS
    +
    {{phone.connectivity.gps | checkmark}}
    +
    +... +
    + + +## Test + +Filters, like any other component, should be tested and these tests are very easy to write. + +__`test/unit/filtersSpec.js`:__ +
    +describe('checkmark filter', function() {
    +
    +  it('should convert boolean values to unicode checkmark or cross', function() {
    +    expect(angular.module.ng.$filter.checkmark(true)).toBe('\u2713');
    +    expect(angular.module.ng.$filter.checkmark(false)).toBe('\u2718');
    +  });
    +})
    +
    + +To run the unit tests, execute the `./scripts/test.sh` script and you should see the following +output. + + Chrome: Runner reset. + .... + Total 4 tests (Passed: 4; Fails: 0; Errors: 0) (3.00 ms) + Chrome 11.0.696.57 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms) + + +# Experiments + +* Let's experiment with some of the {@link api/angular.module.ng.$filter built-in angular filters} and add the +following bindings to `index.html`: + * `{{ "lower cap string" | uppercase }}` + * `{{ {foo: "bar", baz: 23} | json }}` + * `{{ 1304375948024 | date }}` + * `{{ 1304375948024 | date:"MM/dd/yyyy @ h:mma" }}` + +* We can also create a model with an input element, and combine it with a filtered binding. Add +the following to index.html: + + Uppercased: {{ userInput | uppercase }} + + +# Summary + +Now that you have learned how to write and test a custom filter, go to {@link step_10 step 10} to +learn how we can use angular to enhance the phone details page further. + + + diff --git a/docs/content/tutorial/step_10.ngdoc b/docs/content/tutorial/step_10.ngdoc new file mode 100644 index 00000000..73e8b354 --- /dev/null +++ b/docs/content/tutorial/step_10.ngdoc @@ -0,0 +1,140 @@ +@ngdoc overview +@name Tutorial: 10 - Event Handlers +@description + + + + +In this step, you will add a clickable phone image swapper to the phone details page. + + + + + +The phone details view displays one large image of the current phone and several smaller thumbnail +images. It would be great if we could replace the large image with any of the thumbnails just by +clicking on the desired thumbnail image. Let's have a look at how we can do this with angular. + +The most important changes are listed below. You can see the full diff on {@link +https://github.com/angular/angular-phonecat/compare/step-9...step-10 +GitHub}: + + +## Controller + +__`app/js/controllers.js`:__ +
    +...
    +function PhoneDetailCtrl($xhr) {
    +  var self = this;
    +
    +  $xhr('GET', 'phones/' + self.params.phoneId + '.json', function(code, response) {
    +    self.phone = response;
    +    self.mainImageUrl = response.images[0];
    +  });
    +
    +  self.setImage = function(imageUrl) {
    +    self.mainImageUrl = imageUrl;
    +  }
    +}
    +
    +//PhoneDetailCtrl.$inject = ['$xhr'];
    +
    + +In the `PhoneDetailCtrl` controller, we created the `mainImageUrl` model property and set its +default value to the first phone image url. + +We also created a `setImage` controller method to change the value of `mainImageUrl`. + + +## Template + +__`app/partials/phone-detail.html`:__ +
    +
    +
    +...
    +
    +
    +...
    +
    + +We bound the `ng:src` attribute of the large image to the `mainImageUrl` property. + +We also registered an {@link api/angular.directive.ng:click `ng:click`} handler with thumbnail +images. When a user clicks on one of the thumbnail images, the handler will use the `setImage` +controller method to change the value of the `mainImageUrl` property to the url of the thumbnail +image. + + + +## Test + +To verify this new feature, we added two end-to-end tests. One verifies that the main image is set +to the first phone image by default. The second test clicks on several thumbnail images and +verifies that the main image changed appropriately. + +__`test/e2e/scenarios.js`:__ +
    +...
    +  describe('Phone detail view', function() {
    +
    +    beforeEach(function() {
    +      browser().navigateTo('../../app/index.html#/phones/nexus-s');
    +    });
    +
    +
    +    it('should display the first phone image as the main phone image', function() {
    +       expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
    +    });
    +
    +
    +    it('should swap main image if a thumbnail image is clicked on', function() {
    +      element('.phone-thumbs li:nth-child(3) img').click();
    +      expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.2.jpg');
    +
    +      element('.phone-thumbs li:nth-child(1) img').click();
    +      expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
    +    });
    +  });
    +});
    +
    + +You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you +can see them running on {@link +http://angular.github.com/angular-phonecat/step-8/test/e2e/runner.html +angular's server}. + +# Experiments + +* Let's add a new controller method to `PhoneCatCtrl`: + + this.hello = function(name) { + alert('Hello ' + (name || 'world') + '!'); + } + + and add: + + + + to the `index.html` template. + + The controller methods are inherited between controllers/scopes, so you can use the same snippet +in the `phone-list.html` template as well. + +* Move the `hello` method from `PhoneCatCtrl` to `PhoneListCtrl` and you'll see that the button +declared in `index.html` will stop working, while the one declared in the `phone-list.html` +template remains operational. + + +# Summary + +With the phone image swapper in place, we're ready for {@link step_11 step 11} (the last step!) to +learn an even better way to fetch data. + + + diff --git a/docs/content/tutorial/step_11.ngdoc b/docs/content/tutorial/step_11.ngdoc new file mode 100644 index 00000000..c6b70065 --- /dev/null +++ b/docs/content/tutorial/step_11.ngdoc @@ -0,0 +1,208 @@ +@ngdoc overview +@name Tutorial: 11 - REST and Custom Services +@description + + + + +In this step, you will improve the way our app fetches data. + + + + + +The last improvement we will make to our app is to define a custom service that represents a {@link +http://en.wikipedia.org/wiki/Representational_State_Transfer RESTful} client. Using this client we +can make xhr requests for data in an easier way, without having to deal with the lower-level {@link +api/angular.module.ng.$xhr $xhr} API, HTTP methods and URLs. + +The most important changes are listed below. You can see the full diff on {@link +https://github.com/angular/angular-phonecat/compare/step-10...step-11 +GitHub}: + + +## Template + +The custom service is defined in `app/js/services.js` so we need to include this file in our layout +template: + +__`app/index.html`.__ +
    +...
    +  
    +...
    +
    + +## Service + +__`app/js/services.js`.__ +
    + angular.module.ng('Phone', function($resource) {
    +  return $resource('phones/:phoneId.json', {}, {
    +    query: {method: 'GET', params: {phoneId: 'phones'}, isArray: true}
    +  });
    + });
    +
    + +We used the {@link api/angular.module.ng} API to register a custom service. We passed in the name of +the service - 'Phone' - and a factory function. The factory function is similar to a controller's +constructor in that both can declare dependencies via function arguments. The Phone service +declared a dependency on the `$resource` service. + +The {@link api/angular.module.ng.$resource `$resource`} service makes it easy to create a {@link +http://en.wikipedia.org/wiki/Representational_State_Transfer RESTful} client with just a few lines +of code. This client can then be used in our application, instead of the lower-level {@link +api/angular.module.ng.$xhr $xhr} service. + + +## Controller + +We simplified our sub-controllers (`PhoneListCtrl` and `PhoneDetailCtrl`) by factoring out the +lower-level {@link api/angular.module.ng.$xhr $xhr} service, replacing it with a new service called +`Phone`. Angular's {@link api/angular.module.ng.$resource `$resource`} service is easier to use than +{@link api/angular.module.ng.$xhr $xhr} for interacting with data sources exposed as RESTful +resources. It is also easier now to understand what the code in our controllers is doing. + +__`app/js/controllers.js`.__ +
    +...
    +
    +function PhoneListCtrl(Phone) {
    +  this.orderProp = 'age';
    +  this.phones = Phone.query();
    +}
    +//PhoneListCtrl.$inject = ['Phone'];
    +
    +
    +function PhoneDetailCtrl(Phone) {
    +  var self = this;
    +
    +  self.phone = Phone.get({phoneId: self.params.phoneId}, function(phone) {
    +    self.mainImageUrl = phone.images[0];
    +  });
    +
    +  ...
    +}
    +//PhoneDetailCtrl.$inject = ['Phone'];
    +
    + +Notice how in `PhoneListCtrl` we replaced: + + $xhr('GET', 'phones/phones.json', function(code, response) { + self.phones = response; + }); + +with: + + this.phones = Phone.query(); + +This is a simple statement that we want to query for all phones. + +An important thing to notice in the code above is that we don't pass any callback functions when +invoking methods of our Phone service. Although it looks as if the result were returned +synchronously, that is not the case at all. What is returned synchronously is a "future" — an +object, which will be filled with data when the xhr response returns. Because of the data-binding +in angular, we can use this future and bind it to our template. Then, when the data arrives, the +view will automatically update. + +Sometimes, relying on the future object and data-binding alone is not sufficient to do everything +we require, so in these cases, we can add a callback to process the server response. The +`PhoneDetailCtrl` controller illustrates this by setting the `mainImageUrl` in a callback. + + +## Test + +We have modified our unit tests to verify that our new service is issuing HTTP requests and +processing them as expected. The tests also check that our controllers are interacting with the +service correctly. + +The {@link api/angular.module.ng.$resource $resource} service augments the response object with +methods for updating and deleting the resource. If we were to use the standard `toEqual` matcher, +our tests would fail because the test values would not match the responses exactly. To solve the +problem, we use a newly-defined `toEqualData` {@link +http://pivotal.github.com/jasmine/jsdoc/symbols/jasmine.Matchers.html Jasmine matcher}. When the +`toEqualData` matcher compares two objects, it takes only object properties into account and +ignores methods. + + +__`test/unit/controllersSpec.js`:__ +
    +describe('PhoneCat controllers', function() {
    +
    +  beforeEach(function() {
    +    this.addMatchers({
    +      toEqualData: function(expected) {
    +        return angular.equals(this.actual, expected);
    +      }
    +    });
    +  });
    +
    +  describe('PhoneListCtrl', function() {
    +    var scope, $browser, ctrl;
    +
    +    beforeEach(function() {
    +      scope = angular.module.ng.$rootScope.Scope();
    +      $browser = scope.$service('$browser');
    +
    +      $browser.xhr.expectGET('phones/phones.json')
    +          .respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
    +      ctrl = scope.$new(PhoneListCtrl);
    +    });
    +
    +    it('should create "phones" model with 2 phones fetched from xhr', function() {
    +      expect(ctrl.phones).toEqual([]);
    +      $browser.xhr.flush();
    +
    +      expect(ctrl.phones).toEqualData([{name: 'Nexus S'},
    +                                       {name: 'Motorola DROID'}]);
    +    });
    +
    +    it('should set the default value of orderProp model', function() {
    +      expect(ctrl.orderProp).toBe('age');
    +    });
    +  });
    +
    +
    +  describe('PhoneDetailCtrl', function() {
    +    var scope, $browser, ctrl;
    +
    +    beforeEach(function() {
    +      scope = angular.module.ng.$rootScope.Scope();
    +      $browser = scope.$service('$browser');
    +    });
    +
    +    beforeEach(function() {
    +      scope = angular.module.ng.$rootScope.Scope();
    +      $browser = scope.$service('$browser');
    +    });
    +
    +    it('should fetch phone detail', function() {
    +      scope.params = {phoneId:'xyz'};
    +      $browser.xhr.expectGET('phones/xyz.json').respond({name:'phone xyz'});
    +      ctrl = scope.$new(PhoneDetailCtrl);
    +
    +      expect(ctrl.phone).toEqualData({});
    +      $browser.xhr.flush();
    +
    +      expect(ctrl.phone).toEqualData({name:'phone xyz'});
    +    });
    +  });
    +});
    +
    + +To run the unit tests, execute the `./scripts/test.sh` script and you should see the following +output. + + Chrome: Runner reset. + .... + Total 4 tests (Passed: 4; Fails: 0; Errors: 0) (3.00 ms) + Chrome 11.0.696.57 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms) + + +# Summary + +There you have it! We have created a web app in a relatively short amount of time. In the {@link +the_end closing notes} we'll cover were to go from here. + + + diff --git a/docs/content/tutorial/the_end.ngdoc b/docs/content/tutorial/the_end.ngdoc new file mode 100644 index 00000000..ed6eda97 --- /dev/null +++ b/docs/content/tutorial/the_end.ngdoc @@ -0,0 +1,21 @@ +@ngdoc overview +@name Tutorial: The End +@description + +Our application is now complete. Feel free to experiment with the code further, and jump back to +previous steps using the `git checkout` or `goto_step.sh` commands. + +For more details and examples of the angular concepts we touched on in this tutorial, see the +{@link guide/ Developer Guide}. + +For several more examples of code, see the {@link cookbook/ Cookbook}. + +When you are ready to start developing a project using angular, we recommend that you bootstrap +your development with the {@link https://github.com/angular/angular-seed angular seed} project. + +We hope this tutorial was useful to you and that you learned enough about angular to make you want +to learn more. We especially hope you are inspired to go out and develop angular web apps of your +own, and that you might be interested in {@link misc/contribute contributing} to angular. + +If you have questions or feedback or just want to say "hi", please post a message at {@link +https://groups.google.com/forum/#!forum/angular}. -- cgit v1.2.3