導(dǎo)言
最近在學(xué)AngularJS的實例教程PhoneCat Tutorial App,發(fā)現(xiàn)網(wǎng)上的中文教程都比較久遠(yuǎn)麦射,與英文版對應(yīng)不上蛾娶,而且缺少組件和文件重構(gòu)兩節(jié)。所以決定自己整理一個中文簡明教程潜秋。
此篇為10-12節(jié)蛔琅。
上一篇:AngularJS Phonecat (步驟8-步驟9)
10 更多模板
在這一步中,我們將實現(xiàn)手機(jī)詳情視圖半等,當(dāng)用戶手機(jī)列表上的某一項時顯示揍愁。我們將使用的$ HTTP來獲取我們的數(shù)據(jù)呐萨,并修改phoneDetail組件的模板。
數(shù)據(jù)
除了phoens.json莽囤,app/phones/文件也包含每款手機(jī)的JSON文件:
app/phones/nexus-s.json: (一個例子)
{
"additionalFeatures": "Contour Display, Near Field Communications (NFC), ...",
"android": {
"os": "Android 2.3",
"ui": "Android"
},
...
"images": [
"img/phones/nexus-s.0.jpg",
"img/phones/nexus-s.1.jpg",
"img/phones/nexus-s.2.jpg",
"img/phones/nexus-s.3.jpg"
],
"storage": {
"flash": "16384MB",
"ram": "512MB"
}
}
這些文件使用相同的數(shù)據(jù)結(jié)構(gòu)描述手機(jī)的各種特性谬擦,我們要將這些信息顯示到手機(jī)詳情視圖中鳍置。
組件控制器
我們利用$http服務(wù)請求JSON文件床未,來增強(qiáng)手機(jī)詳情組件的控制器任柜。這與手機(jī)列表控件控制器的工作原理相同乳愉。
app/phone-detail/phone-detail.component.js:
angular.
module('phoneDetail').
component('phoneDetail', {
templateUrl: 'phone-detail/phone-detail.template.html',
controller: ['$http', '$routeParams',
function PhoneDetailController($http, $routeParams) {
var self = this;
$http.get('phones/' + $routeParams.phoneId + '.json').then(function(response) {
self.phone = response.data;
});
}
]
});
為了構(gòu)建HTTP的URL請求,我們使用了$routeParams.phoneId剥纷,它是通過$route服務(wù)從當(dāng)前路由中提取出來的锄蹂。
組件模板
前面用占位符粗略定義的模板已經(jīng)被替換成一個成熟的外部模板了袁,該模板包含手機(jī)列表和手機(jī)詳情的數(shù)據(jù)綁定最筒。
注意贺氓,我們使用{{表達(dá)式}}和ngRepeat將數(shù)據(jù)模型中的手機(jī)信息傳送到視圖。
app/phone-detail/phone-detail.template.html:
<img ng-src="{{$ctrl.phone.images[0]}}" class="phone" />
<h1>{{$ctrl.phone.name}}</h1>
<p>{{$ctrl.phone.description}}</p>
<ul class="phone-thumbs">
<li ng-repeat="img in $ctrl.phone.images">
<img ng-src="{{img}}" />
</li>
</ul>
<ul class="specs">
<li>
<span>Availability and Networks</span>
<dl>
<dt>Availability</dt>
<dd ng-repeat="availability in $ctrl.phone.availability">{{availability}}</dd>
</dl>
</li>
...
<li>
<span>Additional Features</span>
<dd>{{$ctrl.phone.additionalFeatures}}</dd>
</li>
</ul>
測試
我們編寫了一個新的單元測試床蜘,與第7節(jié)中phoneList組件控制器的測試類似辙培。
app/phone-detail/phone-detail.component.spec.js:
describe('phoneDetail', function() {
// 在每次測試前,加載包含`phoneDetail`組件的功能
beforeEach(module('phoneDetail'));
// 測試控制器
describe('PhoneDetailController', function() {
var $httpBackend, ctrl;
beforeEach(inject(function($componentController, _$httpBackend_, $routeParams) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/xyz.json').respond({name: 'phone xyz'});
$routeParams.phoneId = 'xyz';
ctrl = $componentController('phoneDetail');
}));
it('should fetch the phone details', function() {
expect(ctrl.phone).toBeUndefined();
$httpBackend.flush();
expect(ctrl.phone).toEqual({name: 'phone xyz'});
});
});
});
我們也增加了一個端到端測試:導(dǎo)航到'Nexus S' 詳情頁邢锯,驗證頁面頭部是否為"Nexus S"扬蕊。
e2e-tests/scenarios.js
...
describe('View: Phone detail', function() {
beforeEach(function() {
browser.get('index.html#!/phones/nexus-s');
});
it('should display the `nexus-s` page', function() {
expect(element(by.binding('$ctrl.phone.name')).getText()).toBe('Nexus S');
});
});
...
命令行中輸入<code>npm run protractor</code>既可運(yùn)行。
11 自定義轉(zhuǎn)換器
這一節(jié)丹擎,我們要創(chuàng)建一個自定義顯示轉(zhuǎn)換器尾抑。
上一節(jié),詳情頁面直接用"true"和"false"來顯示某個手機(jī)特性是否被支持蒂培,在這里再愈,我們將定制一個轉(zhuǎn)換器將字符轉(zhuǎn)成圖形,符號:? 對應(yīng) "true", ? 對應(yīng) "false"毁渗。
檢查標(biāo)識轉(zhuǎn)換器
由于該轉(zhuǎn)換器是通用的(不是只用于單個視圖或組件)践磅,所以我們將它注冊到覆蓋應(yīng)用程序范圍的核心模塊。
app/core/core.module.js:
angular.module('core', []);
app/core/checkmark/checkmark.filter.js:
angular.
module('core').
filter('checkmark', function() {
return function(input) {
return input ? '\u2713' : '\u2718';
};
});
我們的轉(zhuǎn)換器叫做"checkmark"灸异,輸入的值為trur或false。返回結(jié)果是unicode字符:true (\u2713 -> ?) 或者false (\u2718 -> ?)羔飞。
現(xiàn)在轉(zhuǎn)換器已經(jīng)OK肺樟,接著需要注冊其核心模塊,作為主模塊phonecatApp的依賴逻淌。
app/app.module.js:
angular.module('phonecatApp', [
...
'core',
...
]);
模板
我們已經(jīng)創(chuàng)建了兩個新文件(core.module.js, checkmark.filter.js)么伯,還需要將它們引入我們的布局模板中。
app/index.html:
...
<script src="core/core.module.js"></script>
<script src="core/checkmark/checkmark.filter.js"></script>
...
轉(zhuǎn)換器的語法:
{{expression | filter}}
將轉(zhuǎn)換器引入手機(jī)詳情模板:
app/phone-detail/phone-detail.template.html:
...
<dl>
<dt>Infrared</dt>
<dd>{{$ctrl.phone.connectivity.infrared | checkmark}}</dd>
<dt>GPS</dt>
<dd>{{$ctrl.phone.connectivity.gps | checkmark}}</dd>
</dl>
...
測試
app/core/checkmark/checkmark.filter.spec.js:
describe('checkmark', function() {
beforeEach(module('core'));
it('should convert boolean values to unicode checkmark or cross',
//注入轉(zhuǎn)換器
inject(function(checkmarkFilter) {
//檢查轉(zhuǎn)換器字符串與unicode編碼是否對應(yīng)
expect(checkmarkFilter(true)).toBe('\u2713');
expect(checkmarkFilter(false)).toBe('\u2718');
})
);
});
在每次測試前卡儒,beforeEach(module('core')) 加載了核心模板(包含checkmark轉(zhuǎn)換器)田柔。
我們還調(diào)用了輔助功能函數(shù)inject(function(checkmarkFilter) { ... })
來訪問待測試的轉(zhuǎn)換器俐巴。具體功能函數(shù)請參閱angular.mock.inject。
注入時硬爆,轉(zhuǎn)換器名稱需要加后綴"Filter"欣舵。比如,checkmark轉(zhuǎn)化器以checkmarkFilter注入缀磕。更多內(nèi)容缘圈,請參閱Filters。
12 事件處理
在這一步中袜蚕,我們會增加可點(diǎn)擊的手機(jī)圖片糟把,點(diǎn)擊后進(jìn)入手機(jī)詳情頁。手機(jī)詳情視圖展示一個當(dāng)前手機(jī)的大圖和其他手機(jī)的縮略圖牲剃。點(diǎn)擊縮略圖會切換大圖遣疯。
組件控制器
app/phone-detail/phone-detail.component.js:
...
controller: ['$http', '$routeParams',
function PhoneDetailController($http, $routeParams) {
var self = this;
self.setImage = function setImage(imageUrl) {
self.mainImageUrl = imageUrl;
};
$http.get('phones/' + $routeParams.phoneId + '.json').then(function(response) {
self.phone = response.data;
self.setImage(self.phone.images[0]);
});
}
]
...
在phoneDetail控制器中,我們創(chuàng)建了一個mainImageUrl模型屬性凿傅,并且設(shè)置默認(rèn)值為第一個手機(jī)圖片的URL另锋。而setImage()是事件處理程序,用于改變mainImageUrl狭归。
組件模板
app/phone-detail/phone-detail.template.html:
<img ng-src="{{$ctrl.mainImageUrl}}" class="phone" />
...
<ul class="phone-thumbs">
<li ng-repeat="img in $ctrl.phone.images">
<img ng-src="{{img}}" ng-click="$ctrl.setImage(img)" />
</li>
</ul>
...
- 大圖片的ngSrc指令與$ctrl.mainImageUrl屬性綁定夭坪。
- 縮略圖注冊ngClick事件處理程序。當(dāng)用戶點(diǎn)擊縮略圖時过椎,事件處理程序會調(diào)用$ctrl.setImage() 函數(shù)室梅,將$ctrl.mainImageUrl屬性改為縮略圖的url。從而改變大圖內(nèi)容疚宇。
測試
為了驗證新特性亡鼠,增加了兩個端到端測試。一個驗證mainImageUrl默認(rèn)值是第一張手機(jī)圖片的url敷待。另一個驗證點(diǎn)擊縮略圖時间涵,大圖的url會跟著改變(即大圖可正常切換)。
e2e-tests/scenarios.js:
...
describe('View: Phone detail', function() {
...
//驗證大圖是第一張手機(jī)圖片
it('should display the first phone image as the main phone image', function() {
var mainImage = element(by.css('img.phone'));
expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
});
//驗證圖片切換
it('should swap the main image when clicking on a thumbnail image', function() {
var mainImage = element(by.css('img.phone'));
var thumbnails = element.all(by.css('.phone-thumbs img'));
thumbnails.get(2).click();
expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/);
thumbnails.get(0).click();
expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
});
});
...
命令行輸入<code>npm run protractor</code>榜揖,運(yùn)行測試勾哩。
我們還要重構(gòu)單元測試,因為這一步phoneDetial添加了mainImageUrl模型屬性举哟。與之前一樣思劳,我們會在測試中使用模擬響應(yīng)。
app/phone-detail/phone-detail.component.spec.js:
...
describe('controller', function() {
var $httpBackend, ctrl
var xyzPhoneData = {
name: 'phone xyz',
images: ['image/url1.png', 'image/url2.png']
};
beforeEach(inject(function($componentController, _$httpBackend_, _$routeParams_) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData);
...
}));
it('should fetch phone details', function() {
expect(ctrl.phone).toBeUndefined();
$httpBackend.flush();
expect(ctrl.phone).toEqual(xyzPhoneData);
});
});
...
就這樣妨猩,我們的單元測試也完成了潜叛。