現(xiàn)在是時候用AngularJS制作動態(tài)網(wǎng)頁了。我們將添加一個測試,驗證用于控制器的代碼,我們將添加這個控制器。
為應用程序構(gòu)造代碼有很多方式。針對Angular應用,我們鼓勵使用模塊-視圖-控制器(MVC)設(shè)計模式以解耦代碼、分離關(guān)注點??紤]到這一點,我們使用小的Angular以及JavaScript為我們的應用添加模塊、視圖和控制器組件。
把工作空間重置到第二步
git checkout -f step-2
刷新你的瀏覽器或在線檢查這一步:Step 2 Live Demo
下面列出了第一步和第二步之間的最重要的區(qū)別。你可以在GitHub里看到完整的差異。
在Angular中,視圖是模塊透過HTML模板的映射。這意味著每當模塊有變化時,Angular會刷新適當?shù)慕壎c,隨之更新視圖。
以下面代碼為模板,Angular結(jié)構(gòu)化了視圖組件:
app/index.html
:
<html ng-app="phonecatApp">
<head>
...
<script src="/attachments/image/wk/angularjs/angular.js"></script>
<script src="/attachments/image/wk/angularjs/controllers.js"></script>
</head>
<body ng-controller="PhoneListCtrl">
<ul>
<li ng-repeat="phone in phones">
<span>{{phone.name}}</span>
<p>{{phone.snippet}}</p>
</li>
</ul>
</body>
</html>
我們用ngRepeat指令和兩個Angular表達式替代硬編碼的手機列表:
<li>
元素標簽上的元素屬性ng-repeat="phone in phones"
是一個Angular轉(zhuǎn)發(fā)器指令。該轉(zhuǎn)發(fā)器告訴Angular為列表中的每款使用元素標簽<li>
作為模板的手機創(chuàng)建一個<li>
元素。{{phone.name}}
和{{phone.snippet}}
)將被替換成表達式的值。我們已經(jīng)添加了一個新指令,稱為ng-controller
,它給元素標簽<body>附加了一個PhoneListCtrl
控制器。在這個點上:
{{phone.name}}
和{{phone.snippet}}
)表示綁定,在我們的應用程序模塊中參引它們,它們被設(shè)置在我們的PhoneListCtrl
控制器上。數(shù)據(jù)模塊(一個簡單的手機數(shù)列,以對象字面記號法表達)現(xiàn)在在PhoneListCtrl
控制器中實例化了。該控制器只是一個構(gòu)造器函數(shù),需要一個$scope
參數(shù):
app/js/controllers.js
:
var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', function ($scope) {
$scope.phones = [
{'name': 'Nexus S',
'snippet': 'Fast just got faster with Nexus S.'},
{'name': 'Motorola XOOM? with Wi-Fi',
'snippet': 'The Next, Next Generation tablet.'},
{'name': 'MOTOROLA XOOM?',
'snippet': 'The Next, Next Generation tablet.'}
];
});
在這里,我們聲明了一個控制器,稱為PhoneListCtrl
,并把它注冊到一個AngularJS模塊PhonecatApp
中。注意,我們的ng-app
指令(在元素標簽<html>
上)現(xiàn)在指定了phonecatApp
模塊名作為載入的模塊,在引導應用Angular應用程序時載入該模塊。
雖然控制器沒有做太多的事情,但是它扮演了一個至關(guān)重要的角色。通過為我們的上下文提供數(shù)據(jù)模塊,控制器允許我們在模塊和視圖之間建立數(shù)據(jù)綁定。我們在展示、數(shù)據(jù)和邏輯組件之間添加點狀虛線,如下所示:
<body>
元素標簽上,引用了我們的控制器的名稱,PhoneListCtrl
(放置在JavaScript文件controllers.js
上)。PhoneListCtrl
控件在$scope
上附加了手機數(shù)據(jù),把它注入到我們的控制器函數(shù)中。該作用域是根作用域的原型化的后代,在定義應用程序的時候創(chuàng)建了該根作用域。該控制器作用域可以在元素標簽<body ng-controller="PhoneListCtrl">
內(nèi)部的所有綁定位置上可用。一個作用域的概念在Angular中是至關(guān)重要的。作用域可以被視為膠合劑,允許模板、模塊和控制器一起工作。Angular使用作用域,以及模板、數(shù)據(jù)模塊和控制器中包含的信息,以保持模塊和視圖分離,但是同步。任何對模塊的改變會影響視圖;任何在視圖中發(fā)生的改變反應在模塊中。
要想學習更多關(guān)于Angular作用域的知識,請參閱angular作用域文檔。
從視圖中分離控制器的“Angular方法”,使測試代碼變得容易,就像是它在被開發(fā)那樣。如果你的控制器在全局命名空間中可用,則我們可以用一個模擬的scope
對象簡單把它實例化:
test/e2e/scenarios.js
:
describe('PhoneListCtrl', function(){
it('should create "phones" model with 3 phones', function() {
var scope = {},
ctrl = new PhoneListCtrl(scope);
expect(scope.phones.length).toBe(3);
});
});
測試實例化的PhoneListCtrl
并在包含三個記錄的作用域上核查手機數(shù)列屬性。這個示例演示了為Angular中的代碼創(chuàng)建一個單元測試是多么容易。因為測試是軟件開發(fā)的如此至關(guān)重要的部分,我們讓在Angular中創(chuàng)建測試變得容易,從而可以鼓勵開發(fā)員編寫它們。
在實踐中,你應該不想讓你的控制器函數(shù)在全局命名空間內(nèi)。取而代之的是,你可以看到我們已經(jīng)利用一個phonecatApp
模塊上的匿名構(gòu)造器函數(shù)注冊了控制器。
在這種情況下,Angular提供了一個服務(wù),$controller
,它可以以名稱接收你的控制器。這里有使用$controller
同樣的測試:
test/unit/controllersSpec.js
:
describe('PhoneListCtrl', function(){
beforeEach(module('phonecatApp'));
it('should create "phones" model with 3 phones', inject(function($controller) {
var scope = {},
ctrl = $controller('PhoneListCtrl', {$scope:scope});
expect(scope.phones.length).toBe(3);
}));
});
phonecatApp
模塊。$controller
服務(wù)inject
到我們的測試函數(shù)中。$controller
以創(chuàng)建一個PhoneListCtrl
的實例。Angular喜歡使用Jasmine的行為-驅(qū)動開發(fā)(BCC)的句法。雖然Angular沒有要求你使用Jasmine,但是在這個教程中,我們用Jasmine v1.3編寫所有的測試。你可以在Jasmine官方首頁和Jasmine文檔中學習Jasmine。
angular-seed項目是預處理的,以使用Karma運行單元測試,但是你將需要確保已經(jīng)安裝了Karma和它的必要的插件。你可以通過運行rpm install
來做到這。
要想運行測試,請運行rpm test
,然后觀察文件有什么改變。
如果你已經(jīng)在你的機器上安裝了這些瀏覽器中的一個,確保在運行測試之前更新Karma的配置文件。本地配置文件在test/karma.conf.js
,然后更新browsers
屬性。
例如,如果你只安裝了Chrome:
... browsers: ['Chrome'], ...
你將在終端看到以下或者類似的輸出:
info: Karma server started at http://localhost:9876/ info (launcher): Starting browser "Chrome" info (Chrome 22.0): Connected on socket id tPUm9DXcLHtZTKbAEO-n Chrome 22.0: Executed 1 of 1 SUCCESS (0.093 secs / 0.004 secs)
耶!測試通過了!或者沒有通過……
添加對index.html
的另一個綁定。例如:
<p>Total number of phones: {{phones.length}}</p>
在控制器中創(chuàng)建一個新模塊屬性,然后從模板中把它綁定到模塊上。例如:
$scope.name = "World";
然后向index.html
添加一個新的綁定:
<p>Hello, {{name}}!</p>
刷新你的瀏覽器,核實它是否說了"Hello, World!"。
為./test/unit/controllersSpec.js
中的控制器更新單元測試,以反映以前的變化。例如添加:
expect(scope.name).toBe('World');
在index.html
中創(chuàng)建一個重復器,它結(jié)構(gòu)化了一個簡單的表格:
<table>
<tr><th>row number</th></tr>
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i}}</td></tr>
</table>
現(xiàn)在,讓這個基于1的列表的i
在綁定中增值1。
<table>
<tr><th>row number</th></tr>
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
</table>
另需指出:嘗試并使一個8x8的表格使用一個額外的ng-repeat
。
expect(scope.phones.length).toBe(3)
變成toBe(4)
,使單元測試失敗。現(xiàn)在你有了一個動態(tài)的應用,功能分離開模塊、視力和控制器組件,而且你測試了它們?,F(xiàn)在,讓我們前往第三步 篩選迭代器以學習如何為應用添加全文搜索。
更多建議: