diff options
| author | Igor Minar | 2012-03-30 14:02:26 -0700 | 
|---|---|---|
| committer | Igor Minar | 2012-04-04 15:59:18 -0700 | 
| commit | 6336b6e89e3a80aec3c4367ab4c2737fd365c030 (patch) | |
| tree | 31aa86a0555b541d1f6cc107845278ae80ddbff9 /docs/content/tutorial/step_05.ngdoc | |
| parent | fdf17d729fa7651e88dc5f27c40b8de875a34a55 (diff) | |
| download | angular.js-6336b6e89e3a80aec3c4367ab4c2737fd365c030.tar.bz2 | |
chore(docs): restore old tutorial ngdoc files
Diffstat (limited to 'docs/content/tutorial/step_05.ngdoc')
| -rw-r--r-- | docs/content/tutorial/step_05.ngdoc | 216 | 
1 files changed, 216 insertions, 0 deletions
| diff --git a/docs/content/tutorial/step_05.ngdoc b/docs/content/tutorial/step_05.ngdoc new file mode 100644 index 00000000..7bf6f708 --- /dev/null +++ b/docs/content/tutorial/step_05.ngdoc @@ -0,0 +1,216 @@ +@ngdoc overview +@name Tutorial: 5 - XHRs & Dependency Injection +@description + +<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 +injection (DI)} to provide the service to the `PhoneListCtrl` controller. + + +<doc:tutorial-instructions step="5"></doc:tutorial-instructions> + + +You should now see a list of 20 phones. + +The most important changes are listed below. You can see the full diff on {@link +https://github.com/angular/angular-phonecat/compare/step-4...step-5 +GitHub}: + +## Data + +The `app/phones/phone.json` file in your project is a dataset that contains a larger list of phones +stored in the JSON format. + +Following is a sample of the file: +<pre> +[ + { +  "age": 13, +  "id": "motorola-defy-with-motoblur", +  "name": "Motorola DEFY\u2122 with MOTOBLUR\u2122", +  "snippet": "Are you ready for everything life throws your way?" +  ... + }, +... +] +</pre> + + +## 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 +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. + +Services are managed by angular's {@link guide/dev_guide.di DI subsystem}. Dependency injection +helps to make your web apps both well-structured (e.g., separate components for presentation, data, +and control) and loosely coupled (dependencies between components are not resolved by the +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; +  }); + +  self.orderProp = 'age'; +} + +//PhoneListCtrl.$inject = ['$xhr']; +</pre> + +`$xhr` 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! + +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: + +    function PhoneListCtrl($xhr) {...} + +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). + +<img src="img/tutorial/xhr_service_final.png"> + + +### '$' Prefix Naming Convention + +You can create your own services, and in fact we will do exactly that in step 11. As a naming +convention, angular's built-in services, Scope methods and a few other angular APIs have a '$' +prefix in front of the name.  Don't use a '$' prefix when naming your services and models, in order +to avoid any possible naming collisions. + +### A Note on Minification + +Since angular infers the controller's dependencies from the names of arguments to the controller's +constructor function, if you were to {@link http://en.wikipedia.org/wiki/Minification_(programming) +minify} the JavaScript code for `PhoneListCtrl` controller, all of its function arguments would be +minified as well, and the dependency injector would not being able to identify services correctly. + +To overcome issues caused by minification, just assign an array with service identifier strings +into the `$inject` property of the controller function, just like the last line in the snippet +(commented out) suggests: + +    PhoneListCtrl.$inject = ['$xhr']; + + +## Test + +__`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 +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; + +    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); +    }); +  }); +</pre> + +We created the controller in the test environment, as follows: + +* We created a root scope object by calling `angular.module.ng.$rootScope.Scope()` + +* 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 +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. + +* 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. + +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(ctrl.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 make the assertions, verifying that the phone model now exists on the scope. + +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'); +    }); +  }); +}); +</pre> + +To run the unit tests, execute the `./scripts/test.sh` script and you should see the 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) + + +# Experiments + +* At the bottom of `index.html`, add a `{{phones}}` 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: + +         self.phones = response.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 +thumbnail images of phones and some links. + + +<ul doc:tutorial-nav="5"></ul> | 
