aboutsummaryrefslogtreecommitdiffstats
path: root/docs/content/tutorial/step_11.ngdoc
diff options
context:
space:
mode:
authorIgor Minar2012-03-30 14:02:26 -0700
committerIgor Minar2012-04-04 15:59:18 -0700
commit6336b6e89e3a80aec3c4367ab4c2737fd365c030 (patch)
tree31aa86a0555b541d1f6cc107845278ae80ddbff9 /docs/content/tutorial/step_11.ngdoc
parentfdf17d729fa7651e88dc5f27c40b8de875a34a55 (diff)
downloadangular.js-6336b6e89e3a80aec3c4367ab4c2737fd365c030.tar.bz2
chore(docs): restore old tutorial ngdoc files
Diffstat (limited to 'docs/content/tutorial/step_11.ngdoc')
-rw-r--r--docs/content/tutorial/step_11.ngdoc208
1 files changed, 208 insertions, 0 deletions
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
+
+<ul doc:tutorial-nav="11"></ul>
+
+
+In this step, you will improve the way our app fetches data.
+
+
+<doc:tutorial-instructions step="11"></doc:tutorial-instructions>
+
+
+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`.__
+<pre>
+...
+ <script src="js/services.js"></script>
+...
+</pre>
+
+## Service
+
+__`app/js/services.js`.__
+<pre>
+ angular.module.ng('Phone', function($resource) {
+ return $resource('phones/:phoneId.json', {}, {
+ 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.
+
+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`.__
+<pre>
+...
+
+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'];
+</pre>
+
+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`:__
+<pre>
+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'});
+ });
+ });
+});
+</pre>
+
+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.
+
+
+<ul doc:tutorial-nav="11"></ul>