在這一步中,你將改變我們獲取數(shù)據(jù)的方法。
把工作空間重置到第十一步
git checkout -f step-11
刷新你的瀏覽器或在線檢查這一步:Step 8 Live Demo
下面列出了第十步和第十一步之間最重要的區(qū)別。你可以在GitHub上看到完整的差異。
Angular在ngResource
模塊中提供了安靜的功能,它是與核心Angular框架分開分布的。
我們正在使用Bower以安裝客戶端依賴性。這一步更新的bower.json
配置文件,以包含新的依賴性:
{
"name": "angular-seed",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-seed",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.4.x",
"angular-mocks": "1.4.x",
"jquery": "~2.1.1",
"bootstrap": "~3.1.1",
"angular-route": "1.4.x",
"angular-resource": "1.4.x"
}
}
新的依賴性"angular-resource": "1.4.x"
告訴bower安裝一個以angular為源的組件的版本,它與v1.4x版兼容。我們必須要求bower下載并安裝這個依賴性。我們可以通過運行下面的指令來做到它:
npm install
我們的自定義源服務(wù)將被定義在app/js/services.js
中,因此我們需要在我們的布局模板中包含這個文件。另外,我們還需要載入angular-resouces.js
文件,它包含了ngResource模塊:
app/index.html.
...
<script src="/attachments/image/wk/angularjs/angular-resource.js"></script>
<script src="/attachments/image/wk/angularjs/services.js"></script>
...
我們創(chuàng)建了自己的服務(wù),以提供對服務(wù)器上的手機數(shù)據(jù)的訪問:
app/js/services.js.
var phonecatServices = angular.module('phonecatServices', ['ngResource']);
phonecatServices.factory('Phone', ['$resource',
function($resource){
return $resource('phones/:phoneId.json', {}, {
query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
});
}]);
我們使用模塊API,利用工廠函數(shù)注冊自定義的服務(wù)。我們傳入服務(wù)的名稱“Phone”以及工廠函數(shù)。工廠函數(shù)的結(jié)構(gòu)近似于控制器,兩者都可以聲明依賴性,以通過函數(shù)參數(shù)注入。Phone服務(wù)在$resource
服務(wù)上聲明了一個依賴性。
$resource
服務(wù)使它更容易只用寥寥幾行代碼創(chuàng)建一個RESTful客戶端。這種客戶端可以用在我們的應(yīng)用中,代替底層$http服務(wù)。
app/js/app.js.
...
angular.module('phonecatApp', ['ngRoute', 'phonecatControllers','phonecatFilters', 'phonecatServices']).
...
我們需要把phonecatServices
模塊依賴性添加到phonecatApp
模塊的需要數(shù)列中。
通過重構(gòu)掉底層的$http服務(wù),我們簡化了我們的子控制器(PhoneListCtrl
和PhoneDetailCtrl
),用稱為Phone
的服務(wù)替代它。Angular的$resource
服務(wù)比$http
更容易使用,用來與作為REST的源對外提供的數(shù)據(jù)源交互?,F(xiàn)在我們更容易理解控制器中的這些代碼是干什么的了。
app/js/controllers.js.
var phonecatControllers = angular.module('phonecatControllers', []);
...
phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phone', function($scope, Phone) {
$scope.phones = Phone.query();
$scope.orderProp = 'age';
}]);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', 'Phone', function($scope, $routeParams, Phone) {
$scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
$scope.mainImageUrl = phone.images[0];
});
$scope.setImage = function(imageUrl) {
$scope.mainImageUrl = imageUrl;
}
}]);
注意我們把PhoneList
內(nèi)部替換成了什么:
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
換成:
$scope.phones = Phone.query();
我們通過這條簡單語句來查詢所有手機。
一個需要注意的重要事情是,在上面的代碼中,在引用手機服務(wù)的方法的時候,我們沒有傳遞任何回調(diào)函數(shù)。雖然它看起來就像結(jié)果是同步返回的,但其實根本不是。同步返回的是一個“future”——一個對象,當(dāng)XHR響應(yīng)返回的時候,將填入數(shù)據(jù)。因為Angular中的數(shù)據(jù)綁定,我們可以使用這個future并且把它綁定到我們的模板上。然后,當(dāng)數(shù)據(jù)到達的時候,視圖將自動更新。
有些時候,單憑future對象和數(shù)據(jù)綁定不足以滿足我們所有的需求,在那種情況下,我們可以添加一個回調(diào)函數(shù),以處理服務(wù)器響應(yīng)。PhoneDetailCtrl
控制器通過設(shè)置回調(diào)函數(shù)中的mainImageUrl
來演示它。
因為我們現(xiàn)在使用了ngResource模塊,為了用以angular為源更新Karma配置單文件,它是必要的,這樣新測試才能通過。
test/karma.conf.js:
files : [
'app/bower_components/angular/angular.js',
'app/bower_components/angular-route/angular-route.js',
'app/bower_components/angular-resource/angular-resource.js',
'app/bower_components/angular-mocks/angular-mocks.js',
'app/js/**/*.js',
'test/unit/**/*.js'
],
我們已經(jīng)修改了我們的單元測試,以驗證我們的新服務(wù)會發(fā)起HTTP請求,并像預(yù)期那樣處理它們。測試還檢查了我們的控制器正確地與服務(wù)交互。
$resource服務(wù)參增加了帶有用來更新和刪除源的方法的響應(yīng)對象。如果我們打算使用標(biāo)準(zhǔn)的toEqual
匹配器,我們的測試將失敗,因為測試值不能與響應(yīng)嚴(yán)格匹配。要想解決這個問題,我們使用了一個新定義的toEqualData
[Jasmine matcher][jasmine匹配器]。當(dāng)toEqualData
匹配器對比兩個對象的時候,它考慮對象屬性屬性而忽略對象方法。
test/unit/controllersSpec.js:
describe('PhoneCat controllers', function() {
beforeEach(function(){
this.addMatchers({
toEqualData: function(expected) {
return angular.equals(this.actual, expected);
}
});
});
beforeEach(module('phonecatApp'));
beforeEach(module('phonecatServices'));
describe('PhoneListCtrl', function(){
var scope, ctrl, $httpBackend;
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});
}));
it('should create "phones" model with 2 phones fetched from xhr', function() {
expect(scope.phones).toEqualData([]);
$httpBackend.flush();
expect(scope.phones).toEqualData(
[{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
});
it('should set the default value of orderProp model', function() {
expect(scope.orderProp).toBe('age');
});
});
describe('PhoneDetailCtrl', function(){
var scope, $httpBackend, ctrl,
xyzPhoneData = function() {
return {
name: 'phone xyz',
images: ['image/url1.png', 'image/url2.png']
}
};
beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData());
$routeParams.phoneId = 'xyz';
scope = $rootScope.$new();
ctrl = $controller('PhoneDetailCtrl', {$scope: scope});
}));
it('should fetch phone detail', function() {
expect(scope.phone).toEqualData({});
$httpBackend.flush();
expect(scope.phone).toEqualData(xyzPhoneData());
});
});
});
你現(xiàn)在可以在Karma選項卡中看到如下的輸出:
Chrome 22.0: Executed 5 of 5 SUCCESS (0.038 secs / 0.01 secs)
現(xiàn)在我們已經(jīng)看到了如何建立一個自定義的服務(wù),作為REST的客戶端,我們已經(jīng)準(zhǔn)備好前往第十二步 應(yīng)用動畫(最后一步)以學(xué)會如何用動畫提高應(yīng)用程序。
更多建議: