aboutsummaryrefslogtreecommitdiffstats
path: root/docs/content/tutorial/step_05.ngdoc
diff options
context:
space:
mode:
authorIgor Minar2012-04-27 15:18:54 -0700
committerIgor Minar2012-04-30 01:08:15 -0700
commit075c089b5cbe72e95ec96638f8925aeb44824f7c (patch)
treec4502f67a8b5862c31c101152708a6a8d2c35dd1 /docs/content/tutorial/step_05.ngdoc
parent2b87c814ab70eaaff6359ce1a118f348c8bd2197 (diff)
downloadangular.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.
Diffstat (limited to 'docs/content/tutorial/step_05.ngdoc')
-rw-r--r--docs/content/tutorial/step_05.ngdoc134
1 files changed, 77 insertions, 57 deletions
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.