angularjs 教程6
2014-06-15 17:37
211 查看
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-inservices called
We will use Angular'sdependency injection (DI) to provide the service to the
Workspace Reset Instructions ➤
Git on Mac/Linux
Git on Windows
Reset the workspace to step 5.
Refresh your browser or check the app out on
Angular's server.
Reset the workspace to step 5.
Refresh your browser or check the app out on
Angular's server.
You should now see a list of 20 phones.
The most important changes are listed below. You can see the full diff on
GitHub:
Following is a sample of the 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?"
...
},
...
]
them.
Services are managed by Angular's
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).
var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
});
they both look the same. For the sake of simplicity we used a json file in this tutorial.)
The
the json response and parsed it for us!
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:
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.
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
The
If you inspect a Scope, you may also notice some properties that begin with
code for
There are two ways to overcome issues caused by minification:
You can create a
function PhoneListCtrl($scope, $http) {...}
PhoneListCtrl.$inject = ['$scope', '$http'];
phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
Use the inline 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:
function PhoneListCtrl($scope, $http) {...}
phonecatApp.controller
cecb
('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);
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.
When using the second method, it is common to provide the constructor function inline as an anonymous function when registering the controller:
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', function($scope, $http) {...}]);
From this point onward, we're going to use the inline method in the tutorial. With that in mind, let's add the annotations to our
var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http',
function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}]);
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
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
var scope, ctrl, $httpBackend;
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service but then attach it to a variable
// with the same name as the service.
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/phones.json').
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
scope = $rootScope.$new();
ctrl = $controller('PhoneListCtrl', {$scope: scope});
}));
Note: Because we loaded Jasmine and
that we'll use to access and configure the injector.
We created the controller in the test environment, as follows:
We used the
each test is isolated from the work done in other tests.
We created a new scope for our controller by calling
We called the injected
Because our code now uses the
do this we:
Request
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
method.
Now we will make assertions to verify that the
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'}]);
});
We flush the request queue in the browser by calling
We make the assertions, verifying that the phone model now exists on the scope.
Finally, we verify that the default value of
it('should set the default value of orderProp model', function() {
expect(scope.orderProp).toBe('age');
});
You should now see the following output in the Karma tab:
In the
and some links.
$http.
We will use Angular'sdependency injection (DI) to provide the service to the
PhoneListCtrlcontroller.
Workspace Reset Instructions ➤
Git on Mac/Linux
Git on Windows
Reset the workspace to step 5.
git checkout -f step-5
Refresh your browser or check the app out on
Angular's server.
Reset the workspace to step 5.
git checkout -f step-5
Refresh your browser or check the app out on
Angular's server.
You should now see a list of 20 phones.
The most important changes are listed below. You can see the full diff on
GitHub:
Data
Theapp/phones/phones.jsonfile 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:
[
{
"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'll use Angular's$httpservice in our controller to make an HTTP request to your web server to fetch the data in the
app/phones/phones.jsonfile.
$httpis just one of several built-inangular services that handle common operations in web apps. Angular injects these services for you where you need
them.
Services are managed by Angular's
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:
var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
});
$httpmakes an HTTP GET request to our web server, asking for
phone/phones.json(the url is relative to our
index.htmlfile). 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
$httpservice returns a
promise objectwith a
successmethod. 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 dependencies you need as arguments to the controller's constructor function, as follows:
phonecatApp.controller('PhoneListCtrl', function ($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.
$
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.
The
$prefix is there to namespace Angular-provided services. To prevent collisions it's best to avoid naming your services and models anything that begins with a
$.
If you inspect a Scope, you may also notice some properties that begin with
$$. These properties are considered private, and should not be accessed or modified.
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 tominify the JavaScriptcode for
PhoneListCtrlcontroller, all of its function arguments would be minified as well, and the dependency injector would not be able to identify services correctly.
There are two ways to overcome issues caused by minification:
You can create a
$injectproperty on the controller function which holds an array of strings. Each string in the array is the name of the service to inject for the corresponding parameter. In the case of our example we would write:
function PhoneListCtrl($scope, $http) {...}
PhoneListCtrl.$inject = ['$scope', '$http'];
phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
Use the inline 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:
function PhoneListCtrl($scope, $http) {...}
phonecatApp.controller
cecb
('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);
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.
When using the second method, it is common to provide the constructor function inline as an anonymous function when registering the controller:
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', function($scope, $http) {...}]);
From this point onward, we're going to use the inline method in the tutorial. With that in mind, let's add the annotations to our
PhoneListCtrl:
app/js/controllers.js:
var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http',
function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}]);
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
newoperator and provide the constructor with some kind of fake
$httpimplementation. 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:
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
var scope, ctrl, $httpBackend;
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service but then attach it to a variable
// with the same name as the service.
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/phones.json').
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
scope = $rootScope.$new();
ctrl = $controller('PhoneListCtrl', {$scope: scope});
}));
Note: Because we loaded Jasmine and
angular-mocks.jsin our test environment, we got two helper methods
moduleand
inject
that we'll use to access and configure the injector.
We created the controller in the test environment, as follows:
We used the
injecthelper method to inject instances of
$rootScope,
$controllerand
$httpBackendservices into the Jasmine's
beforeEachfunction. 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 the injected
$controllerfunction passing the name of the
PhoneListCtrlcontroller and the created scope as parameters.
Because our code now uses the
$httpservice to fetch the phone list data in our controller, before we create the
PhoneListCtrlchild scope, we need to tell the testing harness to expect an incoming request from the controller. To
do this we:
Request
$httpBackendservice to be injected into our
beforeEachfunction. This is a mock version of the service that in a 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
$httpBackend.expectGETmethod to train the
$httpBackendservice 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
phonesmodel doesn't exist on
scopebefore the response is received:
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'}]);
});
We flush the request queue in the browser by calling
$httpBackend.flush(). This causes the promise returned by the
$httpservice to be resolved 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
orderPropis set correctly:
it('should set the default value of orderProp model', function() {
expect(scope.orderProp).toBe('age');
});
You should now see the following output in the Karma tab:
Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)
Experiments
At the bottom ofindex.html, add a
{{phones | json}}binding to see the list of phones displayed in json format.
In the
PhoneListCtrlcontroller, pre-process the http response by limiting the number of phones to the first 5 in the list. Use the following code in the
$httpcallback:
$scope.phones = data.splice(0, 5);
Summary
Now that you have learned how easy it is to use angular services (thanks to Angular's dependency injection), go tostep 6, where you will add some thumbnail images of phonesand some links.
相关文章推荐
- angularjs 教程14 end
- AngularJS权威教程 笔记(AngularJS是一个很有意思的库,基于函数形参的依赖注入?酷!还有奇怪的$scope和指令)
- AngularJS2.0 教程系列(一)
- AngularJS入门教程之AngularJS指令
- AngularJS入门教程(零):引导程序
- AngularJS入门教程(二):AngularJS模板
- angularJS权威教程自动化测试笔记(二)端到端的介绍
- angularjs 学习教程
- AngularJS入门教程引导程序
- AngularJS入门教程二:在路由中传递参数的方法分析
- [转载]AngularJS入门教程03:迭代器
- JavaScript强化教程——AngularJS 表达式
- Javascript教程:AngularJS的五个超酷特性
- 深入探究AngularJS框架中Scope对象的超级教程
- AngularJS入门教程03:迭代器
- AngularJS入门教程07:路由与多视图
- AngularJS教程 AngularJS从0到1——初识AngularJS
- Yeoman官方教程:用Yeoman和AngularJS做Web应用
- AngularJS入门教程之 XMLHttpRequest实例讲解
- AngularJS入门教程之ng-class 指令用法