So far, we have used three hard-coded mobile record datasets. Now we use AngularJS a built-in service $http to get a larger set of mobile records data. We will use AngularJS' dependency injection (DI) function to provide this AngularJS service to the PhoneListCtrl controller.
Please reset the working directory:
git checkout -f step-5
Refresh the browser and you should now see a list of 20 phones.
The most important differences between Step 4 and Step 5 are listed below. You can see the complete difference in GitHub.
data
The app/phones/phones.json file in your project is a dataset that stores a larger list of phones in JSON format.
Here is an example of this file:
[ { "age": 13, "id": "motorola-defy-with-motoblur", "name": "Motorola DEFY/u2122 with MOTOBLUR/u2122", "snippet": "Are you ready for everything life throws your way?" ... },...]Controller
We use AngularJS service $http in the controller to initiate an HTTP request to your web server to get data from the app/phones/phones.json file. $http is just one of many built-in services in AngularJS that can handle common operations of some web applications. AngularJS can inject these services anywhere you need them.
Services are managed through AngularJS's dependency injection DI subsystem. Dependency injection services can make your web applications well built (such as separating the components of presentation layer, data and control) and loosely coupled (a component does not need to solve the dependency problems between components, they are all handled by the DI subsystem).
app/js/controllers.js
function PhoneListCtrl($scope, $http) { $http.get('phones/phones.json').success(function(data) { $scope.phones = data; }); $scope.orderProp = 'age';}$http initiates an HTTP GET request to the web server and requests phone/phones.json (note that the url is relative to our index.html file). The server responds with data from the json file. (This response may be generated dynamically from the backend server in real time. But for browsers, they all look the same. For simplicity, we simply used a json file in the tutorial.)
The $http service uses success to return [object response][ng.$q]. When the asynchronous response arrives, this object response function is used to process the server response data and assign the data to the scoped phones data model. I noticed that AngularJS will automatically detect this json response and have been parsed for us!
In order to use AngularJS services, you only need to declare the name of the required service as a parameter in the controller constructor, like this:
function PhoneListCtrl($scope, $http) {...}
When the controller is constructed, AngularJS's dependency injector injects these services into your controller. Of course, the dependency injector will also handle any transitive dependencies that the required service may exist (one service will usually depend on other services).
Note that parameter names are very important because injectors will use them to find corresponding dependencies.
'$' prefix naming habit
You can create your own service, and we will actually learn it in step 11. As a naming habit, AngularJS built-in services, scoped methods, and some other AngularJS APIs all use a '$' prefix before the name. Do not use the '$' prefix to name your own services and models, otherwise name conflicts may occur.
About JS compression
Since AngularJS infers the dependency service name through the parameter name of the controller constructor. So if you want to compress the JS code of the PhoneListCtrl controller, all its parameters will be compressed at the same time. At this time, the dependency injection system will not correctly identify the service.
To overcome the problem caused by compression, just assign an array dependent on service identifiers to the $inject property in the controller function, just like the last line commented out:
PhoneListCtrl.$inject = ['$scope', '$http'];
Another method can also be used to specify a dependency list and avoid compression problems - use Javascript array to construct the controller: put the service to be injected into an array of strings (representing the name of the dependency), and the last element of the array is the controller's method function:
var PhoneListCtrl = ['$scope', '$http', function($scope, $http) { /* constructor body */ }];
Both methods mentioned above can work perfectly with any function that AngularJS can inject. Which method to choose depends entirely on the programming style of your project. It is recommended to use array method.
test
test/unit/controllerSpec.js:
Since we are now using dependency injection and our controller also contains many dependency services, it is a little complicated to construct tests for our controllers. We need to use new operations and provide some pseudo implementations to the constructor including $http. However, the recommended method (and even simpler) is to create a controller in a test environment, using the same method as AngularJS does in the following scenario:
describe('PhoneCat controllers', function() { describe('PhoneListCtrl', function(){ var scope, ctrl, $httpBackend; beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) { $httpBackend = _$httpBackend_; $httpBackend.expectGET('phones/phones.json'). response([{name: 'Nexus S'}, {name: 'Motorola DROID'}]); scope = $rootScope.$new(); ctrl = $controller(PhoneListCtrl, {$scope: scope}); }));Note: Because we loaded Jasmine and angular-mock.js in the test environment, we have two helper methods, module and inject, to help us get and configure the injector.
Using the following method, we create a controller in the test environment:
We use the inject method to inject $rootScope, $controller and $httpBackend service instances into Jasmine's beforeEach function. These instances are all from an injector, but this injector is recreated inside each test. This ensures that every test starts from a well-known starting point and that each test is independent of other tests.
Call $rootScope.$new() to create a new scope for our controller.
The PhoneListCtrl function and the scope just created are passed as parameters to the injected $controller function.
Since our current code uses the $http service to obtain the phone list data in the controller before creating the PhoneListCtrl subscope, we need to tell the test suite to wait for a request from the controller. We can do this:
Inject the request service $httpBackend into our beforeEach function. This is a pseudo-version of this service, doing so helps handle all XHR and JSONP requests in the product environment. The pseudo-version of the service allows you to write tests without considering native APIs and global states - any one can constitute a nightmare for testing.
Use the $httpBackend.expectGET method to tell the $httpBackend service to wait for an HTTP request and tell it how to respond to it. Note that the response will not be issued until we call the $httpBackend.flush method.
Now,
it('should create "phones" model with 2 phones fetched from xhr', function() { expect(scope.phones).toBeUndefined(); $httpBackend.flush(); expect(scope.phones).toEqual([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);});In the browser, we call $httpBackend.flush() to clear the (flush) request queue. This will enable the promise returned by the $http service (see here for what promise is) to be interpreted as a standard response.
We set up some assertions to verify that the mobile phone data model is already in scope.
Finally, we verify that the default value of orderProp is set correctly:
it('should set the default value of orderProp model', function() { expect(scope.orderProp).toBe('age');});practise
Add a {{phones | json}} binding at the end of index.html and observe the list of mobile phones in json format.
In the PhoneListCtrl controller, preprocess the HTTP response so that only the first five of the phone list are displayed. Use the following code in the $http callback function:
$scope.phones = data.splice(0, 5);
Summarize
Now you should feel how easy it is to use AngularJS services (this is all thanks to the dependency injection mechanism of AngularJS services), go to step 6 and you will add thumbnails and links to your phone.
Thank you for your support for this website and continue to update related articles in the future!