2011-05-11 00:45:42 +00:00
|
|
|
@ngdoc overview
|
2011-06-06 15:50:35 +00:00
|
|
|
@name Tutorial: 11 - REST and Custom Services
|
2011-05-02 17:16:50 +00:00
|
|
|
@description
|
|
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
<ul doc:tutorial-nav="11"></ul>
|
2011-05-02 17:16:50 +00:00
|
|
|
|
2011-05-11 00:45:42 +00:00
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
In this step, you will improve the way our app fetches data.
|
2011-05-02 17:16:50 +00:00
|
|
|
|
2011-05-11 00:45:42 +00:00
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
<doc:tutorial-instructions step="11"></doc:tutorial-instructions>
|
2011-05-02 17:16:50 +00:00
|
|
|
|
|
|
|
|
|
2011-05-11 00:45:42 +00:00
|
|
|
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
|
2011-11-12 01:15:22 +00:00
|
|
|
api/angular.module.ng.$xhr $xhr} API, HTTP methods and URLs.
|
2011-05-11 00:45:42 +00:00
|
|
|
|
2011-05-02 17:16:50 +00:00
|
|
|
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}:
|
|
|
|
|
|
|
|
|
|
|
2011-05-11 00:45:42 +00:00
|
|
|
## 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`.__
|
2011-05-02 17:16:50 +00:00
|
|
|
<pre>
|
|
|
|
|
...
|
|
|
|
|
<script src="js/services.js"></script>
|
|
|
|
|
...
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
## Service
|
|
|
|
|
|
|
|
|
|
__`app/js/services.js`.__
|
|
|
|
|
<pre>
|
2011-11-12 01:15:22 +00:00
|
|
|
angular.module.ng('Phone', function($resource) {
|
2011-05-02 17:16:50 +00:00
|
|
|
return $resource('phones/:phoneId.json', {}, {
|
2011-06-06 15:50:35 +00:00
|
|
|
query: {method: 'GET', params: {phoneId: 'phones'}, isArray: true}
|
2011-05-02 17:16:50 +00:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
</pre>
|
|
|
|
|
|
2011-11-12 01:15:22 +00:00
|
|
|
We used the {@link api/angular.module.ng} API to register a custom service. We passed in the name of
|
2011-06-06 15:50:35 +00:00
|
|
|
the service - 'Phone' - and a factory function. The factory function is similar to a controller's
|
2011-05-02 17:16:50 +00:00
|
|
|
constructor in that both can declare dependencies via function arguments. The Phone service
|
|
|
|
|
declared a dependency on the `$resource` service.
|
|
|
|
|
|
2011-11-12 01:15:22 +00:00
|
|
|
The {@link api/angular.module.ng.$resource `$resource`} service makes it easy to create a {@link
|
2011-05-02 17:16:50 +00:00
|
|
|
http://en.wikipedia.org/wiki/Representational_State_Transfer RESTful} client with just a few lines
|
2011-06-06 15:50:35 +00:00
|
|
|
of code. This client can then be used in our application, instead of the lower-level {@link
|
2011-11-12 01:15:22 +00:00
|
|
|
api/angular.module.ng.$xhr $xhr} service.
|
2011-05-11 00:45:42 +00:00
|
|
|
|
|
|
|
|
|
2011-05-02 17:16:50 +00:00
|
|
|
## Controller
|
|
|
|
|
|
|
|
|
|
We simplified our sub-controllers (`PhoneListCtrl` and `PhoneDetailCtrl`) by factoring out the
|
2011-11-12 01:15:22 +00:00
|
|
|
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
|
2011-06-06 15:50:35 +00:00
|
|
|
resources. It is also easier now to understand what the code in our controllers is doing.
|
2011-05-11 00:45:42 +00:00
|
|
|
|
2011-05-02 17:16:50 +00:00
|
|
|
__`app/js/controllers.js`.__
|
|
|
|
|
<pre>
|
|
|
|
|
...
|
|
|
|
|
|
2011-08-04 17:48:17 +00:00
|
|
|
function PhoneListCtrl(Phone) {
|
2011-05-02 17:16:50 +00:00
|
|
|
this.orderProp = 'age';
|
2011-08-04 17:48:17 +00:00
|
|
|
this.phones = Phone.query();
|
2011-05-02 17:16:50 +00:00
|
|
|
}
|
|
|
|
|
//PhoneListCtrl.$inject = ['Phone'];
|
|
|
|
|
|
|
|
|
|
|
2011-08-04 17:48:17 +00:00
|
|
|
function PhoneDetailCtrl(Phone) {
|
2011-05-02 17:16:50 +00:00
|
|
|
var self = this;
|
|
|
|
|
|
2011-08-04 17:48:17 +00:00
|
|
|
self.phone = Phone.get({phoneId: self.params.phoneId}, function(phone) {
|
2011-06-07 21:13:44 +00:00
|
|
|
self.mainImageUrl = phone.images[0];
|
2011-05-02 17:16:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
//PhoneDetailCtrl.$inject = ['Phone'];
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
Notice how in `PhoneListCtrl` we replaced:
|
|
|
|
|
|
|
|
|
|
$xhr('GET', 'phones/phones.json', function(code, response) {
|
|
|
|
|
self.phones = response;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
with:
|
|
|
|
|
|
2011-08-04 17:48:17 +00:00
|
|
|
this.phones = Phone.query();
|
2011-05-02 17:16:50 +00:00
|
|
|
|
|
|
|
|
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
|
2011-05-11 00:45:42 +00:00
|
|
|
view will automatically update.
|
|
|
|
|
|
2011-05-02 17:16:50 +00:00
|
|
|
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
|
2011-05-11 00:45:42 +00:00
|
|
|
`PhoneDetailCtrl` controller illustrates this by setting the `mainImageUrl` in a callback.
|
|
|
|
|
|
|
|
|
|
|
2011-05-02 17:16:50 +00:00
|
|
|
## 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.
|
|
|
|
|
|
2011-11-12 01:15:22 +00:00
|
|
|
The {@link api/angular.module.ng.$resource $resource} service augments the response object with
|
2011-06-06 15:50:35 +00:00
|
|
|
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.
|
2011-05-11 00:45:42 +00:00
|
|
|
|
|
|
|
|
|
2011-05-02 17:16:50 +00:00
|
|
|
__`test/unit/controllersSpec.js`:__
|
|
|
|
|
<pre>
|
|
|
|
|
describe('PhoneCat controllers', function() {
|
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
beforeEach(function() {
|
2011-05-02 17:16:50 +00:00
|
|
|
this.addMatchers({
|
|
|
|
|
toEqualData: function(expected) {
|
|
|
|
|
return angular.equals(this.actual, expected);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
describe('PhoneListCtrl', function() {
|
2011-05-02 17:16:50 +00:00
|
|
|
var scope, $browser, ctrl;
|
|
|
|
|
|
|
|
|
|
beforeEach(function() {
|
2011-11-12 01:15:22 +00:00
|
|
|
scope = angular.module.ng.$rootScope.Scope();
|
2011-05-02 17:16:50 +00:00
|
|
|
$browser = scope.$service('$browser');
|
|
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
$browser.xhr.expectGET('phones/phones.json')
|
|
|
|
|
.respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
|
2011-05-02 17:16:50 +00:00
|
|
|
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');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
describe('PhoneDetailCtrl', function() {
|
2011-05-02 17:16:50 +00:00
|
|
|
var scope, $browser, ctrl;
|
|
|
|
|
|
|
|
|
|
beforeEach(function() {
|
2011-11-12 01:15:22 +00:00
|
|
|
scope = angular.module.ng.$rootScope.Scope();
|
2011-05-02 17:16:50 +00:00
|
|
|
$browser = scope.$service('$browser');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
beforeEach(function() {
|
2011-11-12 01:15:22 +00:00
|
|
|
scope = angular.module.ng.$rootScope.Scope();
|
2011-05-02 17:16:50 +00:00
|
|
|
$browser = scope.$service('$browser');
|
|
|
|
|
});
|
|
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
it('should fetch phone detail', function() {
|
2011-05-02 17:16:50 +00:00
|
|
|
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.
|
|
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
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)
|
2011-05-02 17:16:50 +00:00
|
|
|
|
|
|
|
|
|
2011-05-02 23:40:31 +00:00
|
|
|
# Summary
|
2011-05-02 17:16:50 +00:00
|
|
|
|
2011-07-29 19:40:14 +00:00
|
|
|
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.
|
2011-05-02 17:16:50 +00:00
|
|
|
|
|
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
<ul doc:tutorial-nav="11"></ul>
|