diff options
| author | Igor Minar | 2012-04-27 15:18:54 -0700 | 
|---|---|---|
| committer | Igor Minar | 2012-04-30 01:08:15 -0700 | 
| commit | 075c089b5cbe72e95ec96638f8925aeb44824f7c (patch) | |
| tree | c4502f67a8b5862c31c101152708a6a8d2c35dd1 | |
| parent | 2b87c814ab70eaaff6359ce1a118f348c8bd2197 (diff) | |
| download | angular.js-075c089b5cbe72e95ec96638f8925aeb44824f7c.tar.bz2 | |
docs(tutorial): update all the remaining steps
I made some diagrams and portions of the text that are stil stale
invisible. We'll fix these in the next relese.
| -rw-r--r-- | docs/content/tutorial/step_00.ngdoc | 16 | ||||
| -rw-r--r-- | docs/content/tutorial/step_02.ngdoc | 18 | ||||
| -rw-r--r-- | docs/content/tutorial/step_03.ngdoc | 32 | ||||
| -rw-r--r-- | docs/content/tutorial/step_04.ngdoc | 76 | ||||
| -rw-r--r-- | docs/content/tutorial/step_05.ngdoc | 134 | ||||
| -rw-r--r-- | docs/content/tutorial/step_06.ngdoc | 37 | ||||
| -rw-r--r-- | docs/content/tutorial/step_07.ngdoc | 199 | ||||
| -rw-r--r-- | docs/content/tutorial/step_08.ngdoc | 61 | ||||
| -rw-r--r-- | docs/content/tutorial/step_09.ngdoc | 46 | ||||
| -rw-r--r-- | docs/content/tutorial/step_10.ngdoc | 57 | ||||
| -rw-r--r-- | docs/content/tutorial/step_11.ngdoc | 151 | ||||
| -rw-r--r-- | docs/content/tutorial/the_end.ngdoc | 2 | 
12 files changed, 473 insertions, 356 deletions
| diff --git a/docs/content/tutorial/step_00.ngdoc b/docs/content/tutorial/step_00.ngdoc index 63123926..3cf1c172 100644 --- a/docs/content/tutorial/step_00.ngdoc +++ b/docs/content/tutorial/step_00.ngdoc @@ -149,16 +149,17 @@ The code contains some key Angular elements that we will need going forward.  __`app/index.html`:__  <pre>  <!doctype html> -<html ng-app> +<html lang="en" ng-app>  <head>    <meta charset="utf-8"> -  <title>my angular app</title> +  <title>My HTML File</title>    <link rel="stylesheet" href="css/app.css"> +  <link rel="stylesheet" href="css/bootstrap.css">    <script src="lib/angular/angular.js"></script>  </head>  <body> -  Nothing here {{'yet' + '!'}} +  <p>Nothing here {{'yet' + '!'}}</p>  </body>  </html> @@ -245,8 +246,9 @@ scripts and a simple example app, all pre-configured for developing a typical we  For the purposes of this tutorial, we modified the angular-seed with the following changes:  * Removed the example app -* Added phone images to `app/img/phones` -* Added phone data files (JSON) to `app/phones` +* Added phone images to `app/img/phones/` +* Added phone data files (JSON) to `app/phones/` +* Added [Bootstrap](http://twitter.github.com/bootstrap/) files to `app/css/` and `app/img/` @@ -265,9 +267,9 @@ Now let's go to {@link step_01 step 1} and add some content to the web app.  <ul doc:tutorial-nav="0"></ul> -Move elsewhere: - +<div style="display: none">  Note: During the bootstrap the injector and the root scope will then be associated with the     element on which the `ngApp` directive was declared, so when debugging the app you can retrieve     them from browser console via `angular.element(rootElement).scope()` and     `angular.element(rootElement).injector()`. +</div> diff --git a/docs/content/tutorial/step_02.ngdoc b/docs/content/tutorial/step_02.ngdoc index ce5f321c..d7e6a93f 100644 --- a/docs/content/tutorial/step_02.ngdoc +++ b/docs/content/tutorial/step_02.ngdoc @@ -75,12 +75,14 @@ the `PhoneListCtrl` __controller__:  __`app/js/controllers.js`:__  <pre>  function PhoneListCtrl($scope) { -  $scope.phones = [{"name": "Nexus S", -                    "snippet": "Fast just got faster with Nexus S."}, -                   {"name": "Motorola XOOM™ with Wi-Fi", -                    "snippet": "The Next, Next Generation tablet."}, -                   {"name": "MOTOROLA XOOM™", -                    "snippet": "The Next, Next Generation tablet."}]; +  $scope.phones = [ +    {"name": "Nexus S", +     "snippet": "Fast just got faster with Nexus S."}, +    {"name": "Motorola XOOM™ with Wi-Fi", +     "snippet": "The Next, Next Generation tablet."}, +    {"name": "MOTOROLA XOOM™", +     "snippet": "The Next, Next Generation tablet."} +  ];  }  </pre> @@ -120,7 +122,7 @@ __`test/unit/controllersSpec.js`:__  <pre>  describe('PhoneCat controllers', function() { -  describe('PhoneListCtrl', function() { +  describe('PhoneListCtrl', function(){      it('should create "phones" model with 3 phones', function() {        var scope = {}, @@ -163,7 +165,7 @@ execute the tests and report the results in the terminal.               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) +               Chrome 19.0.1084.36 Mac OS: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (2.00 ms)    Yay! The test passed! Or not... diff --git a/docs/content/tutorial/step_03.ngdoc b/docs/content/tutorial/step_03.ngdoc index 4028dcda..60b4173b 100644 --- a/docs/content/tutorial/step_03.ngdoc +++ b/docs/content/tutorial/step_03.ngdoc @@ -32,14 +32,27 @@ We made no changes to the controller.  __`app/index.html`:__  <pre>  ... -   Fulltext Search: <input ng-model="query"> - -  <ul class="phones"> -    <li ng-repeat="phone in phones | filter:query"> -      {{phone.name}} -      <p>{{phone.snippet}}</p> -    </li> -  </ul> +  <div class="container-fluid"> +    <div class="row-fluid"> +      <div class="span2"> +        <!--Sidebar content--> + +        Search: <input ng-model="query"> + +      </div> +      <div class="span10"> +        <!--Body content--> + +        <ul class="phones"> +          <li ng-repeat="phone in phones | filter:query"> +            {{phone.name}} +            <p>{{phone.snippet}}</p> +          </li> +        </ul> + +      </div> +    </div> +  </div>  ...  </pre> @@ -54,7 +67,7 @@ list. This new code demonstrates the following:  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 +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. @@ -86,6 +99,7 @@ describe('PhoneCat App', 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); diff --git a/docs/content/tutorial/step_04.ngdoc b/docs/content/tutorial/step_04.ngdoc index 39e8eeec..900a0340 100644 --- a/docs/content/tutorial/step_04.ngdoc +++ b/docs/content/tutorial/step_04.ngdoc @@ -25,25 +25,21 @@ The most important differences between Steps 3 and 4 are listed below. You can s  __`app/index.html`:__  <pre>  ... -  <ul class="controls"> -    <li> -      Search: <input ng-model="query"> -    </li> -    <li> -      Sort by: -      <select ng-model="orderProp"> -        <option value="name">Alphabetical</option> -        <option value="age">Newest</option> -      </select> -    </li> -  </ul> - -  <ul class="phones"> -    <li ng-repeat="phone in phones | filter:query | orderBy:orderProp"> -      {{phone.name}} -      <p>{{phone.snippet}}</p> -    </li> -  </ul> +        Search: <input ng-model="query"> +        Sort by: +        <select ng-model="orderProp"> +          <option value="name">Alphabetical</option> +          <option value="age">Newest</option> +        </select> + +... + +        <ul class="phones"> +          <li ng-repeat="phone in phones | filter:query | orderBy:orderProp"> +            {{phone.name}} +            <p>{{phone.snippet}}</p> +          </li> +        </ul>  ...  </pre> @@ -72,18 +68,18 @@ necessary!  __`app/js/controller.js`:__  <pre> -/* App Controllers */ -  function PhoneListCtrl($scope) { -  $scope.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}]; +  $scope.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} +  ];    $scope.orderProp = 'age';  } @@ -114,11 +110,11 @@ __`test/unit/controllerSpec.js`:__  <pre>  describe('PhoneCat controllers', function() { -  describe('PhoneListCtrl', function() { -    var scope, $browser, ctrl; +  describe('PhoneListCtrl', function(){ +    var scope, ctrl;      beforeEach(function() { -      scope = {}; +      scope = {},        ctrl = new PhoneListCtrl(scope);      }); @@ -147,7 +143,7 @@ following output.          Chrome: Runner reset.          ..          Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (3.00 ms) -          Chrome 11.0.696.57 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms) +          Chrome 19.0.1084.36 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms)  Let's turn our attention to the end-to-end test. @@ -157,15 +153,14 @@ __`test/e2e/scenarios.js`:__  ...      it('should be possible to control phone order via the drop down select box',          function() { - -      // narrow the dataset to make the test assertions shorter +      //let's narrow the dataset to make the test assertions shorter        input('query').enter('tablet');        expect(repeater('.phones li', 'Phone List').column('phone.name')).            toEqual(["Motorola XOOM\u2122 with Wi-Fi",                     "MOTOROLA XOOM\u2122"]); -      select('orderProp').option('alphabetical'); +      select('orderProp').option('Alphabetical');        expect(repeater('.phones li', 'Phone List').column('phone.name')).            toEqual(["MOTOROLA XOOM\u2122", @@ -183,12 +178,9 @@ Angular's server}.  # Experiments -<div style="display:none"> -!!!!! TODO(i): we need to fix select/option to support unknown option !!!!!  * In the `PhoneListCtrl` controller, remove the statement that sets the `orderProp` value and -you'll see that the ordering as well as the current selection in the dropdown menu will default to -"Alphabetical". -</div> +you'll see that Angular will temporarily add a new "unknown" option to the drop-down list and the +ordering will default to unordered/natural order.  * Add an `{{orderProp}}` binding into the `index.html` template to display its current value as  text. diff --git a/docs/content/tutorial/step_05.ngdoc b/docs/content/tutorial/step_05.ngdoc index a19deed6..d485e8bc 100644 --- a/docs/content/tutorial/step_05.ngdoc +++ b/docs/content/tutorial/step_05.ngdoc @@ -2,14 +2,12 @@  @name Tutorial: 5 - XHRs & Dependency Injection  @description -<h2 style="color: red">This page has not been updated for AngularJS 1.0 yet</h2> -  <ul doc:tutorial-nav="5"></ul>  Enough of building an app with three phones in a hard-coded dataset! Let's fetch a larger dataset  from our server using one of angular's built-in {@link api/angular.module.ng services} called {@link -api/angular.module.ng.$xhr $xhr}. We will use angular's {@link guide/dev_guide.di dependency +api/angular.module.ng.$http $http}. We will use angular's {@link guide/dev_guide.di dependency  injection (DI)} to provide the service to the `PhoneListCtrl` controller. @@ -44,8 +42,8 @@ Following is a sample of the file:  ## Controller -We'll use angular's {@link api/angular.module.ng.$xhr $xhr} service in our controller to make an HTTP -request to your web server to fetch the data in the `app/phones/phones.json` file. `$xhr` is just +We'll use angular's {@link api/angular.module.ng.$http $http} service in our controller to make an HTTP +request to your web server to fetch the data in the `app/phones/phones.json` file. `$http` is just  one of several built-in {@link api/angular.module.ng angular services} that handle common operations  in web apps. Angular injects these services for you where you need them. @@ -56,38 +54,41 @@ components themselves, but by the DI subsystem).  __`app/js/controllers.js:`__  <pre> -function PhoneListCtrl($xhr) { -  var self = this; - -  $xhr('GET', 'phones/phones.json', function(code, response) { -    self.phones = response; +function PhoneListCtrl($scope, $http) { +  $http.get('phones/phones.json').success(function(data) { +    $scope.phones = data;    }); -  self.orderProp = 'age'; +  $scope.orderProp = 'age';  } -//PhoneListCtrl.$inject = ['$xhr']; +//PhoneListCtrl.$inject = ['$scope', '$http'];  </pre> -`$xhr` makes an HTTP GET request to our web server, asking for `phone/phones.json` (the url is +`$http` makes an HTTP GET request to our web server, asking for `phone/phones.json` (the url is  relative to our `index.html` file). The server responds by providing the data in the json file.  (The response might just as well have been dynamically generated by a backend server. To the  browser and our app they both look the same. For the sake of simplicity we used a json file in this  tutorial.) -The `$xhr` service takes a callback as the last argument. This callback is used to process the -response. We assign the response to the scope controlled by the controller, as a model called -`phones`. Notice that angular detected the json response and parsed it for us! +The `$http` service returns a {@link api/angular.module.ng.$q promise object} with a `success` +method. We call this method to handle the asynchronous response and assign the phone data to the +scope controlled by this controller, as a model called `phones`. Notice that angular detected the +json response and parsed it for us! -To use a service in angular, you simply declare the names of the services you need as arguments to -the controller's constructor function, as follows: +To use a service in angular, you simply declare the names of the dependencies you need as arguments +to the controller's constructor function, as follows: -    function PhoneListCtrl($xhr) {...} +    function PhoneListCtrl($scope, $http) {...}  Angular's dependency injector provides services to your controller when the controller is being  constructed. The dependency injector also takes care of creating any transitive dependencies the  service may have (services often depend upon other services). +Note that the names of arguments are significant, because the injector uses these to look up the +dependencies. + +  <img src="img/tutorial/xhr_service_final.png"> @@ -109,7 +110,16 @@ To overcome issues caused by minification, just assign an array with service ide  into the `$inject` property of the controller function, just like the last line in the snippet  (commented out) suggests: -    PhoneListCtrl.$inject = ['$xhr']; +    PhoneListCtrl.$inject = ['$scope', '$http']; + +There is also one more way to specify this dependency list and avoid minification issues — using the +bracket notation which wraps the function to be injected into an array of strings (representing the +dependency names) followed by the function to be injected: + +    var PhoneListCtrl = ['$scope', '$http', function($scope, $http) { /* constructor body */ }]; + +Both of these methods work with any function that can be injected by Angular, so it's up to your +project's style guide to decide which one you use.  ## Test @@ -118,63 +128,73 @@ __`test/unit/controllersSpec.js`:__  Because we started using dependency injection and our controller has dependencies, constructing the  controller in our tests is a bit more complicated. We could use the `new` operator and provide the -constructor with some kind of fake `$xhr` implementation. However, the recommended (and easier) way +constructor with some kind of fake `$http` implementation. However, the recommended (and easier) way  is to create a controller in the test environment in the same way that angular does it in the  production code behind the scenes, as follows:  <pre>  describe('PhoneCat controllers', function() { -  describe('PhoneListCtrl', function() { -    var scope, $browser, ctrl; +  describe('PhoneListCtrl', function(){ +    var scope, ctrl, $httpBackend; -    beforeEach(function() { -      scope = angular.module.ng.$rootScope.Scope(); -      $browser = scope.$service('$browser'); +    beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) { +      $httpBackend = _$httpBackend_; +      $httpBackend.expectGET('phones/phones.json'). +          respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]); -      $browser.xhr.expectGET('phones/phones.json') -          .respond([{name: 'Nexus S'}, -                    {name: 'Motorola DROID'}]); -      ctrl = scope.$new(PhoneListCtrl); -    }); -  }); +      scope = $rootScope.$new(); +      ctrl = $controller(PhoneListCtrl, {$scope: scope}); +    }));  </pre> +Note: Because we loaded Jasmine and `angular-mocks.js` in our test environment, we got two helper +methods {@link api/angular.mock.module module} and {@link api/angular.mock.inject inject} that we'll +use to access and configure the injector. +  We created the controller in the test environment, as follows: -* We created a root scope object by calling `angular.module.ng.$rootScope.Scope()` +* We used the `inject` helper method to inject instances of +{@link api/angular.module.ng.$rootScope $rootScope}, +{@link api/angular.module.ng.$controller $controller} and +{@link api/angular.module.ng.$httpBackend $httpBackend} services into the Jasmine's `beforeEach` +function. These instances come from an injector which is recreated from scratch for every single +test. This guarantees that each test starts from a well known starting point and each test is +isolated from the work done in other tests. + +* We created a new scope for our controller by calling `$rootScope.$new()` -* We called `scope.$new(PhoneListCtrl)` to get angular to create the child scope associated with -the `PhoneListCtrl` controller +* We called `scope.$new(PhoneListCtrl)` to get Angular to create the child scope associated with +the `PhoneListCtrl` controller. -Because our code now uses the `$xhr` service to fetch the phone list data in our controller, before +Because our code now uses the `$http` service to fetch the phone list data in our controller, before  we create the `PhoneListCtrl` child scope, we need to tell the testing harness to expect an  incoming request from the controller. To do this we: -* Use the {@link api/angular.module.ng.$rootScope.Scope#$service `$service`} method to retrieve the `$browser` service, -a service that angular uses to represent various browser APIs. In tests, angular automatically uses -a mock version of this service that allows you to write tests without having to deal with these -native APIs and the global state associated with them. +* Request `$httpBackend` service to be injected into our `beforeEach` function. This is a mock +mock version of the service that in production environment facilitates all XHR and JSONP requests. +The mock version of this service allows you to write tests without having to deal with +native APIs and the global state associated with them — both of which make testing a nightmare. -* Use the `$browser.xhr.expectGET` method to train the `$browser` object to expect an incoming HTTP -request and tell it what to respond with. Note that the responses are not returned before we call -the `$browser.xhr.flush` method. +* Use the `$httpBackend.expectGET` method to train the `$httpBackend` service to expect an incoming +HTTP request and tell it what to respond with. Note that the responses are not returned until we call +the `$httpBackend.flush` method.  Now, we will make assertions to verify that the `phones` model doesn't exist on the scope, before  the response is received:  <pre>      it('should create "phones" model with 2 phones fetched from xhr', function() { -      expect(ctrl.phones).toBeUndefined(); -      $browser.xhr.flush(); +      expect(scope.phones).toBeUndefined(); +      $httpBackend.flush(); -      expect(ctrl.phones).toEqual([{name: 'Nexus S'}, +      expect(scope.phones).toEqual([{name: 'Nexus S'},                                     {name: 'Motorola DROID'}]);      });  </pre> -* We flush the xhr queue in the browser by calling `$browser.xhr.flush()`. This causes the callback -we passed into the `$xhr` service to be executed with the trained response. +* We flush the request queue in the browser by calling `$httpBackend.flush()`. This causes the +promise returned by the `$http` service to be resolved with the trained response.  * We make the assertions, verifying that the phone model now exists on the scope. @@ -182,7 +202,7 @@ Finally, we verify that the default value of `orderProp` is set correctly:  <pre>      it('should set the default value of orderProp model', function() { -      expect(ctrl.orderProp).toBe('age'); +      expect(scope.orderProp).toBe('age');      });    });  }); @@ -194,24 +214,24 @@ output.         Chrome: Runner reset.         ..         Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (3.00 ms) -         Chrome 11.0.696.57 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms) +         Chrome 19.0.1084.36 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms)  # Experiments -* At the bottom of `index.html`, add a `{{phones}}` binding to see the list of phones displayed in -json format. +* At the bottom of `index.html`, add a `{{phones | json}}` binding to see the list of phones +  displayed in json format. -* In the `PhoneListCtrl` controller, pre-process the xhr response by limiting the number of phones -to the first 5 in the list. Use the following code in the xhr callback: +* In the `PhoneListCtrl` controller, pre-process the http response by limiting the number of phones +to the first 5 in the list. Use the following code in the $http callback: -         self.phones = response.splice(0, 5); +         $scope.phones = data.splice(0, 5);  # Summary -Now that you have learned how easy it is to use angular services (thanks to angular's -implementation of dependency injection), go to {@link step_06 step 6}, where you will add some +Now that you have learned how easy it is to use angular services (thanks to Angular's dependency +injection), go to {@link step_06 step 6}, where you will add some  thumbnail images of phones and some links. diff --git a/docs/content/tutorial/step_06.ngdoc b/docs/content/tutorial/step_06.ngdoc index 0cd721d3..770a47de 100644 --- a/docs/content/tutorial/step_06.ngdoc +++ b/docs/content/tutorial/step_06.ngdoc @@ -2,8 +2,6 @@  @name Tutorial: 6 - Templating Links & Images  @description -<h2 style="color: red">This page has not been updated for AngularJS 1.0 yet</h2> -  <ul doc:tutorial-nav="6"></ul> @@ -47,26 +45,27 @@ __`app/phones/phones.json`__ (sample snippet):  __`app/index.html`:__  <pre>  ... -  <ul class="phones"> -    <li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)"> -      <a href="#/phones/{{phone.id}}">{{phone.name}}</a> -      <a href="#/phones/{{phone.id}}" class="thumb"><img ng:src="{{phone.imageUrl}}"></a> -      <p>{{phone.snippet}}</p> -    </li> -  </ul> +        <ul class="phones"> +          <li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail"> +            <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a> +            <a href="#/phones/{{phone.id}}">{{phone.name}}</a> +            <p>{{phone.snippet}}</p> +          </li> +        </ul>  ...  </pre>  To dynamically generate links that will in the future lead to phone detail pages, we used the -now-familiar {@link guide/dev_guide.compiler.markup double-curly brace markup} in the `href` -attribute values. In step 2, we added the `{{phone.name}}` binding as the element content. In this -step the `{{phone.id}}` binding is used in the element attribute. +now-familiar double-curly brace binding in the `href` attribute values. In step 2, we added the +`{{phone.name}}` binding as the element content. In this step the `{{phone.id}}` binding is used in +the element attribute.  We also added phone images next to each record using an image tag with the {@link -api/angular.directive.ng:src ng:src} directive. That directive prevents the browser from treating -the angular `{{ expression }}` markup literally, which it would have done if we had only specified -an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`). Using -`ng:src` prevents the browser from making an http request to an invalid location. +api/angular.module.ng.$compileProvider.directive.ngSrc ngSrc} directive. That directive prevents the +browser from treating the angular `{{ expression }}` markup literally, and initiating a request to +invalid url `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had only +specified an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`). +Using `ngSrc` (`ng-src`) prevents the browser from making an http request to an invalid location.  ## Test @@ -77,7 +76,7 @@ __`test/e2e/scenarios.js`__:      it('should render phone specific links', function() {        input('query').enter('nexus');        element('.phones li a').click(); -      expect(browser().location().hash()).toBe('/phones/nexus-s'); +      expect(browser().location().url()).toBe('/phones/nexus-s');      });  ...  </pre> @@ -92,10 +91,10 @@ angular's server}.  # Experiments -* Replace the `ng:src` directive with a plain old `<src>` attribute. Using tools such as Firebug, +* Replace the `ng-src` directive with a plain old `src` attribute. Using tools such as Firebug,  or Chrome's Web Inspector, or inspecting the webserver access logs, confirm that the app is indeed  making an extraneous request to `/app/%7B%7Bphone.imageUrl%7D%7D` (or -`/app/index.html/{{phone.imageUrl}}`). +`/app/{{phone.imageUrl}}`).  # Summary diff --git a/docs/content/tutorial/step_07.ngdoc b/docs/content/tutorial/step_07.ngdoc index 1cef6f83..dbbf010d 100644 --- a/docs/content/tutorial/step_07.ngdoc +++ b/docs/content/tutorial/step_07.ngdoc @@ -2,8 +2,6 @@  @name Tutorial: 7 - Routing & Multiple Views  @description -<h2 style="color: red">This page has not been updated for AngularJS 1.0 yet</h2> -  <ul doc:tutorial-nav="7"></ul> @@ -14,8 +12,9 @@ multiple views by adding routing.  <doc:tutorial-instructions step="7"></doc:tutorial-instructions> -Note that you are redirected to `app/index.html#/phones` and the same phone list appears in the -browser. When you click on a phone link the stub of a phone detail page is displayed. +Note that when you now navigate to `app/index.html`, you are redirected to `app/index.html#/phones` +and the same phone list appears in the browser. When you click on a phone link the stub of a phone +detail page is displayed.  The most important changes are listed below. You can see the full diff on {@link @@ -36,42 +35,61 @@ template into what we call a "layout template". This is a template that is commo  our application. Other "partial templates" are then included into this layout template depending on  the current "route" — the view that is currently displayed to the user. -Application routes in angular are declared via the {@link api/angular.module.ng.$route $route} -service. This service makes it easy to wire together controllers, view templates, and the current +Application routes in angular are declared via the +{@link api/angular.module.ng.$routeProvider $routeProvider}, which is the provider of the +{@link api/angular.module.ng.$route $route service}. This service makes it easy to wire together +controllers, view templates, and the current  URL location in the browser. Using this feature we can implement {@link  http://en.wikipedia.org/wiki/Deep_linking deep linking}, which lets us utilize the browser's  history (back and forward navigation) and bookmarks. -## Controllers +### A Note About DI, Injector and Providers -__`app/js/controller.js`:__ -<pre> -function PhoneCatCtrl($route) { -  var self = this; +As you noticed the dependency injection is the core feature of AngularJS, so it's important for you +to understand a thing or two about how it works. -  $route.when('/phones', -      {template: 'partials/phone-list.html',   controller: PhoneListCtrl}); -  $route.when('/phones/:phoneId', -      {template: 'partials/phone-detail.html', controller: PhoneDetailCtrl}); -  $route.otherwise({redirectTo: '/phones'}); +When the application bootstraps, Angular creates an injector that will be used for all DI stuff in +this app. The injector itself doesn't know anything about what `$http` or `$route` services do, in +fact it doesn't even know about the existence of these services unless it is configured with proper +module definitions. The sole responsibilities of the injector are to load specified module +definition(s), register all service providers defined in these modules and when asked inject +a specified function with dependencies (services) that it lazily instantiates via their providers. -  $route.onChange(function() { -    self.params = $route.current.params; -  }); +Providers are objects that provide (create) instances of services and expose configuration apis +that can be used to control the creation and runtime behavior of a service. In case of the `$route` +service, the `$routeProvider` exposes apis that allow you to define routes for your application. -  $route.parent(this); -} +Angular modules solve the problem of removing global state from the application and provide a way +of configuring the injector. As opposed to AMD or require.js modules, Angular modules don't try to +solve the problem of script load ordering or lazy script fetching. These goals are orthogonal and +both module systems can live side by side and fulfil their goals. -//PhoneCatCtrl.$inject = ['$route']; -... +## The App Module + +__`app/js/app.js`:__ +<pre> +angular.module('phonecat', []). +  config(['$routeProvider', function($routeProvider) { +  $routeProvider. +      when('/phones', {template: 'partials/phone-list.html',   controller: PhoneListCtrl}). +      when('/phones/:phoneId', {template: 'partials/phone-detail.html', controller: PhoneDetailCtrl}). +      otherwise({redirectTo: '/phones'}); +}]);  </pre> -We created a new controller called `PhoneCatCtrl`. We declared its dependency on the `$route` -service and used this service to declare that our application consists of two different views: +In order to configure our application with routes, we need to create a module for our application. +We call this module `phonecatApp` and using the `config` api we request the `$routeProvider` to be +injected into our config function and use `$routeProvider.when` api to define our routes. + +Note that during the injector configuration phase, the providers can be injected as well, but they +will not be available for injection once the injector is created and starts creating service +instances. + +Our application routes were defined as follows:  * The phone list view will be shown when the URL hash fragment is `/phones`. To construct this -view, angular will use the `phone-list.html` template and the `PhoneListCtrl` controller. +view, Angular will use the `phone-list.html` template and the `PhoneListCtrl` controller.  * The phone details view will be shown when the URL hash fragment matches '/phone/:phoneId', where  `:phoneId` is a variable part of the URL. To construct the phone details view, angular will use the @@ -83,78 +101,107 @@ empty `PhoneDetailCtrl` controller to the `app/js/controllers.js` file for the p  The statement `$route.otherwise({redirectTo: '/phones'})` triggers a redirection to `/phones` when  the browser address doesn't match either of our routes. -Thanks to the `$route.parent(this);` statement and `ng:controller="PhoneCatCtrl"` declaration in -the `index.html` template, the `PhoneCatCtrl` controller has a special role in our app. It is the -"root" controller and the parent controller for the other two sub-controllers (`PhoneListCtrl` and -`PhoneDetailCtrl`). The sub-controllers inherit the model properties and behavior from the root -controller. -  Note the use of the `:phoneId` parameter in the second route declaration. The `$route` service uses  the route declaration — `'/phones/:phoneId'` — as a template that is matched against the current -URL. All variables defined with the `:` notation are extracted into the `$route.current.params` map. +URL. All variables defined with the `:` notation are extracted into the +{@link api/angular.module.ng.$routeParams $routeParams} object. + + +In order for our application to bootstrap with our newly created module we'll also need to specify +the module name as the value of the {@link api/angular.module.ng.$compileProvider.directive.ngApp ngApp} +directive: + +__`app/index.html`:__ +<pre> +<!doctype html> +<html ng-app="phonecat"> +... +</pre> -The `params` alias created in the {@link api/angular.module.ng.$route `$route.onChange`} callback -allows us to use the `phoneId` property of this map in the `phone-details.html` template. + +## Controllers + +__`app/js/controller.js`:__ +<pre> +... +function PhoneDetailCtrl($scope, $routeParams) { +  $scope.phoneId = $routeParams.phoneId; +} + +//PhoneDetailCtrl.$inject = ['$scope', '$routeParams']; +</pre>  ## Template -The `$route` service is usually used in conjunction with the {@link api/angular.widget.ng:view -ng:view} widget. The role of the `ng:view` widget is to include the view template for the current +The `$route` service is usually used in conjunction with the {@link api/angular.module.ng.$compileProvider.directive.ngView +ngView} directive. The role of the `ngView` directive is to include the view template for the current  route into the layout template, which makes it a perfect fit for our `index.html` template.  __`app/index.html`:__  <pre> -<html ng:app> +<html ng-app="phonecat"> +<head>  ... -<body ng:controller="PhoneCatCtrl"> +  <script src="lib/angular/angular.js"></script> +  <script src="js/app.js"></script> +</head> +<body> -  <ng:view></ng:view> +  <div ng-view></div> -  <script src="lib/angular/angular.js"></script> -  <script src="js/controllers.js"></script>  </body>  </html>  </pre>  Note that we removed most of the code in the `index.html` template and replaced it with a single -line containing the `ng:view` tag. The code that we removed was placed into the `phone-list.html` -template: +line containing a div with `ng-view` attribute. The code that we removed was placed into the +`phone-list.html` template:  __`app/partials/phone-list.html`:__  <pre> -<ul class="predicates"> -  <li> -    Search: <input type="text" ng:model="query"/> -  </li> -  <li> -    Sort by: -    <select ng:model="orderProp"> -      <option value="name">Alphabetical</option> -      <option value="age">Newest</option> -    </select> -  </li> -</ul> - -<ul class="phones"> -  <li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)"> -    <a href="#/phones/{{phone.id}}">{{phone.name}}</a> -    <a href="#/phones/{{phone.id}}" class="thumb"><img ng:src="{{phone.imageUrl}}"></a> -    <p>{{phone.snippet}}</p> -  </li> -</ul> +<div class="container-fluid"> +  <div class="row-fluid"> +    <div class="span2"> +      <!--Sidebar content--> + +      Search: <input ng-model="query"> +      Sort by: +      <select ng-model="orderProp"> +        <option value="name">Alphabetical</option> +        <option value="age">Newest</option> +      </select> + +    </div> +    <div class="span10"> +      <!--Body content--> + +      <ul class="phones"> +        <li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail"> +          <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a> +          <a href="#/phones/{{phone.id}}">{{phone.name}}</a> +          <p>{{phone.snippet}}</p> +        </li> +      </ul> + +    </div> +  </div> +</div>  </pre> +<div style="display:none"> +TODO!  <img src="img/tutorial/tutorial_07_final.png"> +</div>  We also added a placeholder template for the phone details view:  __`app/partials/phone-detail.html`:__  <pre> -TBD: detail view for {{params.phoneId}} +TBD: detail view for {{phoneId}}  </pre> -Note how we are using `params` model defined in the `PhoneCatCtrl` controller. +Note how we are using `phoneId` model defined in the `PhoneDetailCtrl` controller.  ## Test @@ -165,21 +212,21 @@ to various URLs and verify that the correct view was rendered.  <pre>  ...    it('should redirect index.html to index.html#/phones', function() { -   browser().navigateTo('../../app/index.html'); -   expect(browser().location().hash()).toBe('/phones'); +    browser().navigateTo('../../app/index.html'); +    expect(browser().location().url()).toBe('/phones');    });  ...   describe('Phone detail view', function() { -   beforeEach(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'); -   }); +    it('should display placeholder page with phoneId', function() { +      expect(binding('phoneId')).toBe('nexus-s'); +    });   });  </pre> @@ -194,14 +241,16 @@ angular's server}.  * 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 `<ng:view>` element. If you add the -same binding into the `phone-list.html` template, the binding will work as expected. +scope managed by `PhoneListCtrl`, which is associated with the `<div ng-view>` element. If you add +the same binding into the `phone-list.html` template, the binding will work as expected. +<div style="display: none">  * 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 `<p>hero = {{hero}}</p>` 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. +</div>  # Summary diff --git a/docs/content/tutorial/step_08.ngdoc b/docs/content/tutorial/step_08.ngdoc index ad206452..9ccaf9b4 100644 --- a/docs/content/tutorial/step_08.ngdoc +++ b/docs/content/tutorial/step_08.ngdoc @@ -2,8 +2,6 @@  @name Tutorial: 8 - More Templating  @description -<h2 style="color: red">This page has not been updated for AngularJS 1.0 yet</h2> -  <ul doc:tutorial-nav="8"></ul> @@ -17,8 +15,8 @@ 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. +To implement the phone details view we will use {@link api/angular.module.ng.$http $http} 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 @@ -58,44 +56,42 @@ 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 +We'll expand the `PhoneDetailCtrl` by using the `$http` service to fetch the json files. This works  the same way as the phone list controller.  __`app/js/controller.js`:__  <pre> -function PhoneDetailCtrl($xhr) { -  var self = this; - -  $xhr('GET', 'phones/' + self.params.phoneId + '.json', function(code, response) { -    self.phone = response; +function PhoneDetailCtrl($scope, $routeParams, $http) { +  $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) { +    $scope.phone = data;    });  } -//PhoneDetailCtrl.$inject = ['$xhr']; +//PhoneDetailCtrl.$inject = ['$scope', '$routeParams', '$http'];  </pre> -To construct the URL for the HTTP request, we use `params.phoneId` extracted from the current route -in the `PhoneCatCtrl` controller. +To construct the URL for the HTTP request, we use `$routeParams.phoneId` extracted from the current +route by the `$route` service.  ## 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 +Note where we use the angular `{{expression}}` markup and `ngRepeater`s to project phone data from  our model into the view.  __`app/partials/phone-details.html`:__  <pre> -<img ng:src="{{phone.images[0]}}" class="phone"/> +<img ng-src="{{phone.images[0]}}" class="phone">  <h1>{{phone.name}}</h1>  <p>{{phone.description}}</p>  <ul class="phone-thumbs"> -  <li ng:repeat="img in phone.images"> -    <img ng:src="{{img}}"/> +  <li ng-repeat="img in phone.images"> +    <img ng-src="{{img}}">    </li>  </ul> @@ -104,7 +100,7 @@ __`app/partials/phone-details.html`:__      <span>Availability and Networks</span>      <dl>        <dt>Availability</dt> -      <dd ng:repeat="availability in phone.availability">{{availability}}</dd> +      <dd ng-repeat="availability in phone.availability">{{availability}}</dd>      </dl>    </li>      ... @@ -115,7 +111,10 @@ __`app/partials/phone-details.html`:__  </ul>  </pre> +<div style="display: none"> +TODO!  <img src="img/tutorial/tutorial_08-09_final.png"> +</div>  ## Test @@ -125,16 +124,26 @@ step 5.  __`test/unit/controllerSpec.js`:__  <pre>  ... -    it('should fetch phone detail', function() { -      scope.params = {phoneId:'xyz'}; -      $browser.xhr.expectGET('phones/xyz.json').respond({name:'phone xyz'}); -      ctrl = scope.$new(PhoneDetailCtrl); +  describe('PhoneDetailCtrl', function(){ +    var scope, $httpBackend, ctrl; + +    beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) { +      $httpBackend = _$httpBackend_; +      $httpBackend.expectGET('phones/xyz.json').respond({name:'phone xyz'}); -      expect(ctrl.phone).toBeUndefined(); -      $browser.xhr.flush(); +      $routeParams.phoneId = 'xyz'; +      scope = $rootScope.$new(); +      ctrl = $controller(PhoneDetailCtrl, {$scope: scope}); +    })); -      expect(ctrl.phone).toEqual({name:'phone xyz'}); + +    it('should fetch phone detail', function() { +      expect(scope.phone).toBeUndefined(); +      $httpBackend.flush(); + +      expect(scope.phone).toEqual({name:'phone xyz'});      }); +  });  ...  </pre> @@ -144,7 +153,7 @@ 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) +      Chrome 19.0.1084.36 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 diff --git a/docs/content/tutorial/step_09.ngdoc b/docs/content/tutorial/step_09.ngdoc index f4aa010a..45418cec 100644 --- a/docs/content/tutorial/step_09.ngdoc +++ b/docs/content/tutorial/step_09.ngdoc @@ -2,8 +2,6 @@  @name Tutorial: 9 - Filters  @description -<h2 style="color: red">This page has not been updated for AngularJS 1.0 yet</h2> -  <ul doc:tutorial-nav="9"></ul> @@ -26,13 +24,15 @@ 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. +In order to create a new filter, you are going to create a `phonecatFilters` module and register +your custom filter with this module:  __`app/js/filters.js`:__  <pre> -angular.module.ng.$filter('checkmark', function(input) { -  return input ? '\u2713' : '\u2718'; +angular.module('phonecatFilters', []).filter('checkmark', function() { +  return function(input) { +    return input ? '\u2713' : '\u2718'; +  };  });  </pre> @@ -40,6 +40,16 @@ The name of our filter is "checkmark". The `input` evaluates to either `true` or  return one of two unicode characters we have chosen to represent true or false (`\u2713` and  `\u2718`). +Now that our filter is ready, we need to register the `phonecatFilters` module as a dependency for +our main `phonecat` module. + +__`app/js/app.js`:__ +<pre> +... +angular.module('phonecat', ['phonecatFilters']). +... +</pre> +  ## Template @@ -81,22 +91,32 @@ Filters, like any other component, should be tested and these tests are very eas  __`test/unit/filtersSpec.js`:__  <pre> -describe('checkmark filter', function() { +describe('filter', function() { + +  beforeEach(module('phonecatFilters')); + -  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'); +  describe('checkmark', function() { + +    it('should convert boolean values to unicode checkmark or cross', +        inject(function(checkmarkFilter) { +      expect(checkmarkFilter(true)).toBe('\u2713'); +      expect(checkmarkFilter(false)).toBe('\u2718'); +    }));    }); -}) +});  </pre> +Note that you need to configure our test injector with the `phonecatFilters` module before any of +our filter tests execute. +  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) +          Chrome 19.0.1084.36 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms)  # Experiments @@ -111,7 +131,7 @@ following bindings to `index.html`:  *  We can also create a model with an input element, and combine it with a filtered binding. Add  the following to index.html: -        <input ng:model="userInput"> Uppercased: {{ userInput | uppercase }} +        <input ng-model="userInput"> Uppercased: {{ userInput | uppercase }}  # Summary diff --git a/docs/content/tutorial/step_10.ngdoc b/docs/content/tutorial/step_10.ngdoc index dd31da82..1d60de06 100644 --- a/docs/content/tutorial/step_10.ngdoc +++ b/docs/content/tutorial/step_10.ngdoc @@ -2,8 +2,6 @@  @name Tutorial: 10 - Event Handlers  @description -<h2 style="color: red">This page has not been updated for AngularJS 1.0 yet</h2> -  <ul doc:tutorial-nav="10"></ul> @@ -27,52 +25,53 @@ GitHub}:  __`app/js/controllers.js`:__  <pre>  ... -function PhoneDetailCtrl($xhr) { -  var self = this; - -  $xhr('GET', 'phones/' + self.params.phoneId + '.json', function(code, response) { -    self.phone = response; -    self.mainImageUrl = response.images[0]; +function PhoneDetailCtrl($scope, $routeParams, $http) { +  $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) { +    $scope.phone = data; +    $scope.mainImageUrl = data.images[0];    }); -  self.setImage = function(imageUrl) { -    self.mainImageUrl = imageUrl; +  $scope.setImage = function(imageUrl) { +    $scope.mainImageUrl = imageUrl;    }  } -//PhoneDetailCtrl.$inject = ['$xhr']; +//PhoneDetailCtrl.$inject = ['$scope', '$routeParams', '$http'];  </pre>  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`. +We also created a `setImage` event handler function that will change the value of `mainImageUrl`.  ## Template  __`app/partials/phone-detail.html`:__  <pre> -<img ng:src="{{mainImageUrl}}" class="phone"/> +<img ng-src="{{mainImageUrl}}" class="phone">  ...  <ul class="phone-thumbs"> -  <li ng:repeat="img in phone.images"> -    <img ng:src="{{img}}" ng:click="setImage(img)"> +  <li ng-repeat="img in phone.images"> +    <img ng-src="{{img}}" ng-click="setImage(img)">    </li>  </ul>  ...  </pre> -We bound the `ng:src` attribute of the large image to the `mainImageUrl` property. +We bound the `ngSrc` directive 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. +We also registered an {@link api/angular.module.ng.$compileProvider.directive.ngClick `ngClick`} +handler with thumbnail images. When a user clicks on one of the thumbnail images, the handler will +use the `setImage` event handler function to change the value of the `mainImageUrl` property to the +url of the thumbnail image. +<div style="display: none"> +TODO!  <img src="img/tutorial/tutorial_10-11_final.png"> +</div>  ## Test @@ -85,13 +84,10 @@ __`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'); +      expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');      }); @@ -113,24 +109,27 @@ angular's server}.  # Experiments -* Let's add a new controller method to `PhoneCatCtrl`: +* Let's add a new controller method to `PhoneDetailCtrl`: -          this.hello = function(name) { +          $scope.hello = function(name) {                alert('Hello ' + (name || 'world') + '!');            }    and add: -          <button ng:click="hello('Elmo')">Hello</button> +          <button ng-click="hello('Elmo')">Hello</button> -  to the `index.html` template. +  to the `phone-details.html` template. +<div style="display: none"> +TODO!    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. +</div>  # Summary diff --git a/docs/content/tutorial/step_11.ngdoc b/docs/content/tutorial/step_11.ngdoc index e2826805..acad5467 100644 --- a/docs/content/tutorial/step_11.ngdoc +++ b/docs/content/tutorial/step_11.ngdoc @@ -2,8 +2,6 @@  @name Tutorial: 11 - REST and Custom Services  @description -<h2 style="color: red">This page has not been updated for AngularJS 1.0 yet</h2> -  <ul doc:tutorial-nav="11"></ul> @@ -16,7 +14,7 @@ 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. +api/angular.module.ng.$http $http} 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 @@ -26,12 +24,14 @@ GitHub}:  ## Template  The custom service is defined in `app/js/services.js` so we need to include this file in our layout -template: +template. Additionally, we also need to load the `angular-resource.js` file, which contains the +`ngResource` module and in it the `$resource` service, that we'll soon use:  __`app/index.html`.__  <pre>  ...    <script src="js/services.js"></script> +  <script src="lib/angular/angular-resource.js"></script>  ...  </pre> @@ -39,64 +39,68 @@ __`app/index.html`.__  __`app/js/services.js`.__  <pre> - angular.module.ng('Phone', function($resource) { +angular.module('phonecatServices', ['ngResource']). +    factory('Phone', function($resource){    return $resource('phones/:phoneId.json', {}, { -    query: {method: 'GET', params: {phoneId: 'phones'}, isArray: true} +    query: {method:'GET', params:{phoneId:'phones'}, isArray:true}    }); - }); +});  </pre> -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. +We used the module API to register a custom service using a factory function. We passed in the name +of the service - 'Phone' - and the 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. +The {@link api/angular.module.ngResource.$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.$http $http} 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. +lower-level {@link api/angular.module.ng.$http $http} service, replacing it with a new service called +`Phone`. Angular's {@link api/angular.module.ngResource.$resource `$resource`} service is easier to +use than `$http 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`.__  <pre>  ... -function PhoneListCtrl(Phone) { -  this.orderProp = 'age'; -  this.phones = Phone.query(); +function PhoneListCtrl($scope, Phone) { +  $scope.phones = Phone.query(); +  $scope.orderProp = 'age';  } -//PhoneListCtrl.$inject = ['Phone']; +//PhoneListCtrl.$inject = ['$scope', 'Phone']; -function PhoneDetailCtrl(Phone) { -  var self = this; -  self.phone = Phone.get({phoneId: self.params.phoneId}, function(phone) { -    self.mainImageUrl = phone.images[0]; + +function PhoneDetailCtrl($scope, $routeParams, Phone) { +  $scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) { +    $scope.mainImageUrl = phone.images[0];    }); -  ... +  $scope.setImage = function(imageUrl) { +    $scope.mainImageUrl = imageUrl; +  }  } -//PhoneDetailCtrl.$inject = ['Phone']; + +//PhoneDetailCtrl.$inject = ['$scope', '$routeParams', 'Phone'];  </pre>  Notice how in `PhoneListCtrl` we replaced: -    $xhr('GET', 'phones/phones.json', function(code, response) { -      self.phones = response; +    $http.get('phones/phones.json').success(function(data) { +      $scope.phones = data;      });  with: -    this.phones = Phone.query(); +    $scope.phones = Phone.query();  This is a simple statement that we want to query for all phones. @@ -118,10 +122,10 @@ We have modified our unit tests to verify that our new service is issuing HTTP r  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 +The {@link api/angular.module.ngResource.$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. @@ -131,7 +135,7 @@ __`test/unit/controllersSpec.js`:__  <pre>  describe('PhoneCat controllers', function() { -  beforeEach(function() { +  beforeEach(function(){      this.addMatchers({        toEqualData: function(expected) {          return angular.equals(this.actual, expected); @@ -139,54 +143,63 @@ describe('PhoneCat controllers', function() {      });    }); -  describe('PhoneListCtrl', function() { -    var scope, $browser, ctrl; -    beforeEach(function() { -      scope = angular.module.ng.$rootScope.Scope(); -      $browser = scope.$service('$browser'); +  beforeEach(module('phonecatServices')); + + +  describe('PhoneListCtrl', function(){ +    var scope, ctrl, $httpBackend; + +    beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) { +      $httpBackend = _$httpBackend_; +      $httpBackend.expectGET('phones/phones.json'). +          respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]); + +      scope = $rootScope.$new(); +      ctrl = $controller(PhoneListCtrl, {$scope: scope}); +    })); -      $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(scope.phones).toEqual([]); +      $httpBackend.flush(); -      expect(ctrl.phones).toEqualData([{name: 'Nexus S'}, -                                       {name: 'Motorola DROID'}]); +      expect(scope.phones).toEqualData( +          [{name: 'Nexus S'}, {name: 'Motorola DROID'}]);      }); +      it('should set the default value of orderProp model', function() { -      expect(ctrl.orderProp).toBe('age'); +      expect(scope.orderProp).toBe('age');      });    }); -  describe('PhoneDetailCtrl', function() { -    var scope, $browser, ctrl; +  describe('PhoneDetailCtrl', function(){ +    var scope, $httpBackend, ctrl, +        xyzPhoneData = function() { +          return { +            name: 'phone xyz', +                images: ['image/url1.png', 'image/url2.png'] +          } +        }; -    beforeEach(function() { -      scope = angular.module.ng.$rootScope.Scope(); -      $browser = scope.$service('$browser'); -    }); -    beforeEach(function() { -      scope = angular.module.ng.$rootScope.Scope(); -      $browser = scope.$service('$browser'); -    }); +    beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) { +      $httpBackend = _$httpBackend_; +      $httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData()); + +      $routeParams.phoneId = 'xyz'; +      scope = $rootScope.$new(); +      ctrl = $controller(PhoneDetailCtrl, {$scope: scope}); +    })); -    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(); +    it('should fetch phone detail', function() { +      expect(scope.phone).toEqualData({}); +      $httpBackend.flush(); -      expect(ctrl.phone).toEqualData({name:'phone xyz'}); +      expect(scope.phone).toEqualData(xyzPhoneData());      });    });  }); @@ -198,7 +211,7 @@ 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) +      Chrome 19.0.1084.36 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms)  # Summary diff --git a/docs/content/tutorial/the_end.ngdoc b/docs/content/tutorial/the_end.ngdoc index f392452e..ed6eda97 100644 --- a/docs/content/tutorial/the_end.ngdoc +++ b/docs/content/tutorial/the_end.ngdoc @@ -2,8 +2,6 @@  @name Tutorial: The End  @description -<h2 style="color: red">This page has not been updated for AngularJS 1.0 yet</h2> -  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. | 
