預計完成時間:60分鐘
在這期的Code Lab中,你將會使用Yeoman和AngularJS搭建一個功能完整的應用,示例應用將會簡短地介紹一些關於Yeoman、Grunt和Bower的特點。如果你有一些程式設計經驗的話,這期的CodeL Lab會很適合你。
認識YEOMAN
Yeoman的logo是一個戴帽子的男人。Yeoman通過整合了三種工具來提升你的工作效率:
- yo是一個用於構建特定框架的生態系統的程式碼工具,我們稱之為生成器(generator)。在以前的文章裡我提過一些繁瑣的任務,使用yo就能處理一些那樣的任務。
- 多虧了Yeoman團隊和grunt-contrib的幫助,在專案的建立、預覽以及測試可以使用grunt。
- bower用於依賴管理,使用它以後你再也不用手動地去一個個下載你的前端庫了。
只需要一兩個命令,Yeoman就能給你的應用提供一個程式碼模板(或者一個像模組一樣的獨立的程式碼片段)、編譯Sass以及在當前的目錄啟動一個簡易的伺服器。它還能做單元測試,壓縮以及合併你的CSS、JS、HTML還有圖片等等功能。 你能使用npm安裝生成器,現在可用的生成器數量已經超過了450,這其中很多都是由開源社群主持編寫的。比較受歡迎的有generator-angular、generator-backbone、generator-ember。
今日YEOMAN應用:使用AngularJS構建的Todo應用
AngularJS是一個用於開發動態Web應用的JavaScript框架。如果要開發一個Web應用,AngularJS能夠操作HTML使之動態地發生改變,而不是一個單純的靜態文件,它提供了像資料繫結和依賴注入(DI)這樣的高階特定來簡化應用的開發。 如果想要了解更多關於AngularJS的資訊,請看文件。 現在讓我們開始著手做下面這個Todo應用吧。
專案原始碼
這個練習的原始碼你可以在http://bit.ly/yoangular的’angular-localStorage-todos’資料夾下找到。記住!你需要在目錄下執行
1 2 |
$ npm install $ bower install |
後再測試這個應用。如果你覺得這些步驟太麻煩了,這裡有幾個指向’angular-localStorage-todos/app’’目錄中的檔案的連結,你可以使用直接它們:)
設定
在安裝Yeoman之前,你需要確認以下配置:
- Node.js 版本在0.10以上
- npm 版本在1.3.7以上
安裝好Node之後,你就可以用命令列來安裝Yeoman了。 注意:大部分情況下Yeoman是要通過命令列來操作的,不同的系統執行以下命令的地方不太一樣:Mac下請使用終端,Linux下使用shell,Windows下使用Cygwin。
1 |
$ npm install --global yo |
如果你看到了’permission errors’或者’access errors’,你需要在這條命令前面加上’sudo’。通過
1 |
$ yo --version && bower --version && grunt --version |
命令來檢查是不是所有東西都已經安裝好了。在執行完上述命令後,你應該會看到有四個版本號會被列印出來:
- Yeoman
- Bower
- Grunt
- Grunt CLI(Grunt的命令列介面)
適用本教程的Yeoman, Bower和Grunt版本
現在的版本迭代太快了,這個教程的測試還是在Yeoman1.1.2, Bower1.2.8以及grunt-cli v0.1.11和grunt0.4.2下完成的。如果你在新的版本上做這個教程的時候出現了問題,就開一個issue來討論,請不要有什麼顧慮。
安裝Yeoman生成器
在傳統的Web開發流程中,你可能會花很多時間在配置程式碼模板、下載依賴還有手動元件專案檔案結構上。而Yeoman就是來簡化這個流程的!前面說的那些繁重的工作都會被交給Yeoman來完成。讓我們來試試用Yeoman來建立一個AngularJS專案吧! 用下面這行命令進入Yeoman的選單:
1 |
$ yo |
用鍵盤的上下鍵來操作選單,當選項’install a generator’被高亮的時候按下Enter鍵。 接下來我們需要尋找一個合適的生成器。搜尋’angular’的話,你會得到很多搜尋結果。這些生成器都是由許多Yeoman開源社群貢獻的。在這個例子裡,我們使用的是’generator-angular’。當選中了’generator-angular’後,按下回車執行安裝,它所依賴的Node包就會開始被下載了。
如果你知道要安裝的生成器的名字,你可以直接用npm來安裝:
1 |
$ npm install -g generator-angular |
下面是一張預覽圖:
這個例子使用的generator-angular版本
技術更新的如此之快!這個教程是在generator-angular 0.7.1環境下測試的。在安裝完Yeoman和Angular生成器後,在命令列中執行’yo’你就可以檢視版本號了。如果你在新版本的generator-angular做這個教程時候出現了問題,請開一個issue,我們很樂意幫助你。或者你可以直接安裝0.7.1這個版本:
1 |
$ npm install -g generator-angular@0.7.1 |
使用生成器搭建你的應用
你可以在Yeoman的選單中操作已經安裝好的生成器:
1 |
$ yo |
等一下!不要直接就執行生成器了。重新建立一個新的專案目錄,生成器會在這個目錄下生成出你的專案檔案的。
1 2 |
$ mkdir mytodo $ cd mytodo |
執行’yo’,選中’Run the Angular generator’,執行生成器。當你比較熟悉Yo的時候,就可以不通過選單直接執行生成器:
1 |
$ yo angular |
一些生成器也會提供一些有共同開發庫(common developer libraries)的可選配置來定製你的應用,能夠加速初始化你的開發環境。 generator-angular會詢問你需不需要使用Sass和/或者Bootstrap,使用’n’和’y’進行選擇。
然後你需要選擇你需要使用的Angular模組。Angular模組是一些帶有特定功能的獨立的JS檔案。舉個例子,ngResource模組(angular-resource.js)提供了RESTful服務。你可以使用空格鍵來取消專案。下面來看一看預設值。(當你在試用空格的效果時,確保所有的模組都被標記為綠色)
好的,現在按下Enter鍵。Yeoman將會自動構建你的應用、拉取需要的依賴並在你的工作流中建立一些有幫助的Grunt任務(Grunt Tasks)。幾分鐘後,我們就能正式開始啦!
由Yeoman構建的檔案目錄結構
開啟’mytodo’目錄,你會看到下面的檔案結構:
- app/:Web應用的父級目錄。
- index.html: Angular應用的基準HTML檔案(base html file)
- 404.html、favicon.ico和robots.txt:通用的Web檔案,Yeoman已經將它建立出來了,你不需要再手動去建立
- bower_components:存放專案相關的JavaScript或Web依賴,由bower安裝的
- scripts:我們的JS檔案
- app.js:主程式
- controllers:Angular控制器
- styles:我們的CSS檔案
- views:Angular模板
- Gruntfile.js、package.json 以及node_modules:Grunt需要使用的依賴以及配置。
- test和karma.conf.js/karma-e2e.conf.js:測試框架以及針對這個專案的單元測試,包括了為控制器寫的樣板測試(boilerplate tests)。
在瀏覽器中檢視你的應用
要在瀏覽器中檢視應用,就要將grunt server執行起來:
1 |
grunt serve |
執行命令後本地會啟動一個基於Node的http服務。通過瀏覽器訪問http://localhost:9000就可以看到你的應用了。現在可以開啟編輯器開始更改應用。每次儲存更改後,瀏覽器將會自動重新整理,就是說你是不需要手動再重新整理瀏覽器了。這個被稱作‘live reloading’,這提供了一個很好的方式來實時檢視應用的狀態。它是通過一系列的Grunt任務來監視你的檔案的更改情況,一旦發現檔案被改動了,’live reloading’就會自動重新整理應用。在這個例子中,我們編輯了views/main.html,通過’live reload’我們從下面的狀態:馬上到了這個狀態:
現在讓我們開始編寫我們的AngularJS應用吧
在app/mytodo目錄中的檔案你都可以在瀏覽器中訪問到,所有要更改的檔案也都在app目錄下。如果你不確定你的檔案建構是否正確,請檢視上文的截圖。
- 建立新模板展現Todo列表
先將views/main.html中除了class是jumbotron的div以外的內容都刪除,然後把’jumbotron’替換成’container’:
1 |
<div class="container"></div> |
更改Angular控制器模板(即scripts/controller/main.js),將’awesomeThings’改為’todos’:
1 2 3 4 5 |
'use strict'; angular.module('mytodoApp') .controller('MainCtrl', function ($scope) { $scope.todos = ['Item 1', 'Item 2', 'Item 3']; }); |
然後更改檢視(views/main.html)來顯示我們的Todo事項:
1 2 3 4 5 6 |
<div class"container"> <h2>My todos</h2> <p ng-repeat="todo in todos"> <input type="text" ng-model="todo"> </p> </div> |
在p標籤中的ng-repeat屬性是一個Angular指令(directive),當獲取到一個集合(collection)中的項時,它將項例項化。在我們的例子中,你可以想象一下,每個p標籤和它的內容都帶著這個’ng-repeat‘屬性。對於每個在todos陣列中的項,Angular都會生成一組新的
1 |
<p><input></p> |
ng-model是另一個Angular指令,它主要是和input、select、textarea標籤和一些自定義控制元件一起使用,達到資料雙向繫結的效果。在我們的例子中,它用於顯示一系列帶有’todo‘的值的文字輸入域。 在瀏覽器中檢視ng-repeat和ng-model動態變化的效果。在儲存之前,我們的應用看起來應該是下圖這個樣子的:
- 新增一個Todo事項
我們將要實現新增新的Todo事項的功能。現在需要修改views/main.html:在h2元素和p元素之間加上一個form元素。現在你的views/main.html應該是下面這個樣子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<div class="container"> <h2>My todos</h2> <!-- Todos input --> <form role="form" ng-submit="addTodo()"> <div class="row"> <div class="input-group"> <input type="text" ng-model="todo" placeholder="What needs to be done?" class="form-control"> <span class="input-group-btn"> <input type="submit" value="Add" class="btn btn-primary"> </span> </div> </div> </form> <p></p> <!-- Todos list --> <p ng-repeat="todo in todos" class="form-group"> <input type="text" ng-model="todo" class="form-control"> </p> </div> |
這一步裡我們在頁面頂部增加了一個帶有提交按鈕的表單。這個表單使用了另一個Angular指令:ng-submit。返回檢視你的瀏覽器,現在的UI應該是下面這個這樣子的:
如果現在點選’Add’按鈕,什麼事情都不會發生—— 我們接下來要實現新增的效果: ng-submit是將一個Angular表示式繫結到表單的onsubmit事件上。如果form上沒有繫結任何動作,它也會阻止瀏覽器的預設行為。在我們的例子中,我們新增了一個’addTodo()’表示式。 下面的addTodo
方法是實現將新增的Todo事項新增入已有的事項列表中,然後清空頂部的文字輸入域:
1 2 3 4 |
$scope.addTodo = function () { $scope.todos.push($scope.todo); $scope.todo = ''; }; |
將addTodo()方法加到scripts/controllers/main.js的MainCtrl控制器的定義中,現在你的控制器程式碼應該如下所示:
1 2 3 4 5 6 7 8 9 10 |
'use strict'; angular.module('mytodoApp') .controller('MainCtrl', function ($scope) { $scope.todos = ['Item 1', 'Item 2', 'Item 3']; $scope.addTodo = function () { $scope.todos.push($scope.todo); $scope.todo = ''; }; }); |
注意:你可能會在在終端/命令列中看到很多linting errors,這可能是因為jshint檢查出程式碼縮排有問題,所以丟擲了警告,事實上這個Todo應用是可以正常工作的。不過,建議還是要看看jshint給你的建議,然後調整你的程式碼讓它更乾淨更有可讀性。 再次在瀏覽器中檢視,然後在頂部的輸入框中輸入新的Todo事項按下’Add’按鈕。新增的這個事項就會立刻出現在你的Todo列表中!
注意:如果你新增了一個空的Todo事項,或者一個重名的事項,你的Todo應用就會罷工哦。:( 不過作為一個小練習,你可以加強一下addTodo()方法來避免上述的錯誤
- 移除一個Todo事項
現在我們來新增一個移除事項的功能,先在列表中每一個Todo事項的的邊上加上一個“移除”按鈕。回到我們的檢視模板(views/main.html),在現有的ng-repeat指令上新增一個按鈕。然後確認我們的輸入框和移除按鈕是對齊的,將p標籤的class從’form-group’改成’input-group’。 再改動之前程式碼是這樣的:
1 2 3 4 |
<!-- Todos list --> <p ng-repeat="todo in todos" class="form-group"> <input type="text" ng-model="todo" class="form-control"> </p> |
改動以後的程式碼:
1 2 3 4 5 6 7 |
<!-- Todos list --> <p class="input-group" ng-repeat="todo in todos"> <input type="text" ng-model="todo" class="form-control"> <span class="input-group-btn"> <button class="btn btn-danger" ng-click="removeTodo($index)" aria-label="Remove">X</button> </span> </p> |
現在你的應用看起來很不錯了!
在上面的程式碼中我們使用了一個新的Angular指令——ng-click。可以用ng-click來控制元素被點選時的行為。在這個例子中,我們呼叫了removeTodo()方法並將$index傳入了這個方法。 $index的值是當前todo項在整個todo陣列中的位置的索引值。舉個例子,陣列中的第一項的索引值是0,那麼0就會被傳入removeTodo();類似的,在一個五項的Todo列表中,最後一項的索引值是4,4就會被傳入removeTodo()。 現在我們來實現這個removeTodo()方法,下面的程式碼是使用JS中的splice方法將要移除的項通過給定的$index值從陣列中移除:
1 2 3 |
$scope.removeTodo = function (index) { $scope.todos.splice(index, 1); }; |
修改以後的控制器(scripts/controller/main.js)如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 |
'use strict'; angular.module('mytodoApp') .controller('MainCtrl', function ($scope) { $scope.todos = ['Item 1', 'Item 2', 'Item 3']; $scope.addTodo = function () { $scope.todos.push($scope.todo); $scope.todo = ''; }; $scope.removeTodo = function (index) { $scope.todos.splice(index, 1); }; }); |
當你更新好程式碼再去檢視瀏覽器的時候,你的應用是下面這個樣子的,剛剛我們又新添了一項:
現在你可以點選’×’按鈕將一個Todo事項從列表中移除。超神奇!
你可能會注意到一點:現在雖然我們可以新增和移除Todo事項,但是這些記錄都不能永久地儲存。一旦頁面被重新整理了,更改的記錄都會不見了,又恢復到我們main.js中設定的todo陣列的值。不過不要擔心這個問題,等我們瞭解更多關於使用Bower安裝package了以後,這個問題就會被解決的。
使用Bower安裝包
現在給我們的列表新增一些排列方式來合理地顯示它。所以我們要使用Bower安裝了一個Angular元件,叫做AngularUI Sortable module。用下面的指令我們可以檢查現在已經安裝上的package:
1 |
$ bower list |
為了確認有AngularUI的包可以使用,用Bower查詢’angular-ui-sortable’:
1 |
$ bower search angular-ui-sortable |
搜尋結果中只有一個和*angular-ui-sortable*有關。而且我們已經安裝了jQuery,那麼現在我們就一起把jQuery UI也一起安裝上。為了節省搜尋的時間,jQuery UI的包的名字是’jquery-ui’。要一次性安裝上它們使用下面的命令:
1 |
$ bower install --save angular-ui-sortable jquery-ui |
使用–save更新bower.json檔案中關於angular-ui-sortable和jquery-ui的依賴,這樣你就不用手動去bower.json中更新依賴了。 看一下你的bower_components目錄是不是所有包都已經拉下來了,你可以看到’jquery-ui’和’angular-ui-sortable’出現在之前已經安裝的Angular包邊上了:
- 讓你的Todo應用可排序(使用Angular Sortable模組)這些新安裝的依賴要被新增進我們的index.html檔案。你可以手動新增,不過其實Yeoman會自動新增上。先在命令列中使用Ctrl+c,退出當前的程式。再次執行:
1$ grunt serve
你可以看到在index.html檔案底部連結指令碼的位置,jquery-ui/ui/jquery-ui.js和angular-ui-sortable/sortable.js已經自動地被引入了。
12345678910111213<!-- build:js scripts/vendor.js --><!-- bower:js --><script src="bower_components/jquery/jquery.js"></script><script src="bower_components/angular/angular.js"></script><script src="bower_components/bootstrap/dist/js/bootstrap.js"></script><script src="bower_components/angular-resource/angular-resource.js"></script><script src="bower_components/angular-cookies/angular-cookies.js"></script><script src="bower_components/angular-sanitize/angular-sanitize.js"></script><script src="bower_components/angular-route/angular-route.js"></script><script src="bower_components/jquery-ui/ui/jquery-ui.js"></script><script src="bower_components/angular-ui-sortable/sortable.js"></script><!-- endbower --><!-- endbuild -->
為了使用Sortable模組,我們需要在scripts/app.js中更新Angular模組,將Sortable可以載入到我們的應用中,更改前程式碼如下所示:
123456angular.module('mytodoApp', ['ngCookies','ngResource','ngSanitize','ngRoute'])
將’ui.sortable’新增進陣列中。現在scripts/app.js應該是下面這個樣子:
123456789101112131415161718'use strict';angular.module('mytodoApp', ['ngCookies','ngResource','ngSanitize','ngRoute','ui.sortable']).config(function ($routeProvider) {$routeProvider.when('/', {templateUrl: 'views/main.html',controller: 'MainCtrl'}).otherwise({redirectTo: '/'});});
最後,在main.html中,我們需要將’ui-sortable’指令作為一個層將ng-repeat層包起來。
123<!-- Todos list --><div ui-sortable ng-model="todos"><p class="input-group" ng-repeat="todo in todos">
我們也需要新增一些內聯的CSS,將滑鼠顯示為“可移動”樣式來告訴使用者這些Todo事項是可以移動的:
1<p ng-repeat="todo in todos" style="padding:5px 10px; cursor: move;">
現在Todo列表的HTML部分應該是下面這樣的:
123456789<!-- Todos list --><div ui-sortable ng-model="todos"><p class="input-group" ng-repeat="todo in todos" style="padding:5px 10px; cursor: move;"><input type="text" ng-model="todo" class="form-control"><span class="input-group-btn"><button class="btn btn-danger" ng-click="removeTodo($index)" aria-label="Remove">X</button></span></p></div>
現在這個列表就是可以移動的了:可以將它移動成這個樣子:
動畫的gif圖片(注意游標):
- 使用本地儲存實現儲存
現在回到剛剛提到的無法儲存的問題上。
Angular有一個模組叫做’angular-local-storage’可以很簡便地幫我們實現本地儲存,使用Bower安裝:
1 |
$ bower install --save angular-local-storage |
與我們新增jQueryUI和AngularUI Sortable的時候相似,我們需要將angular-local-storage.js引入index.html:
1 |
<script src="bower_components/angular-local-storage/angular-local-storage.js"></script> |
因為我們是使用bower.json來索引我們的模組的,所以先用Ctrl+c退出當前的程式,然後再次輸入grunt serve讓Yeoman將新邏輯寫入你的index.html,重新啟動服務後現在你的index.html的指令碼部分看起來應該是下面的樣子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!-- build:js scripts/vendor.js --> <!-- bower:js --> <script src="bower_components/jquery/jquery.js"></script> <script src="bower_components/angular/angular.js"></script> <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script> <script src="bower_components/angular-resource/angular-resource.js"></script> <script src="bower_components/angular-cookies/angular-cookies.js"></script> <script src="bower_components/angular-sanitize/angular-sanitize.js"></script> <script src="bower_components/angular-route/angular-route.js"></script> <script src="bower_components/jquery-ui/ui/jquery-ui.js"></script> <script src="bower_components/angular-ui-sortable/sortable.js"></script> <script src="bower_components/angular-local-storage/angular-local-storage.js"></script> <!-- endbower --> <!-- endbuild --> |
將localStorage寫入scripts/app.js中:
1 2 3 4 5 6 7 8 |
angular.module('mytodoApp', [ 'ngCookies', 'ngResource', 'ngSanitize', 'ngRoute', 'ui.sortable', 'LocalStorageModule' ]) |
在app.js中也要配置’localStorageServiceProvider’,將’todo’作為本地儲存的字首,這樣你的應用就不會把其他應用中重名的變數的內容獲取過來:
1 2 3 |
.config(['localStorageServiceProvider', function(localStorageServiceProvider){ localStorageServiceProvider.setPrefix('ls'); }]) |
我們的scripts/app.js現在應該是這樣的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
'use strict'; angular.module('mytodoApp', [ 'ngCookies', 'ngResource', 'ngSanitize', 'ngRoute', 'ui.sortable', 'LocalStorageModule' ]) .config(['localStorageServiceProvider', function(localStorageServiceProvider){ localStorageServiceProvider.setPrefix('ls'); }]) .config(function ($routeProvider) { $routeProvider .when('/', { templateUrl: 'views/main.html', controller: 'MainCtrl' }) .otherwise({ redirectTo: '/' }); }); |
你也需要在你的控制器(scripts/controllers/main.js)中宣告對本地儲存服務的依賴。將’localStorageService’作為第二個傳入引數新增到你的回撥函式中。
1 2 3 4 5 |
'use strict'; angular.module('mytodoApp') .controller('MainCtrl', function ($scope, localStorageService) { // (code hidden here to save space) }); |
那麼現在呢,我們的Todo事項就不是從靜態的陣列中讀取的,我們將會從本地儲存裡讀取然後再將它們存入$scope.todos中。 我們還需要使用Angular的$warch監聽器來監聽$scope.todos的值得變化。如果有人新增或者刪減了Todo專案,本地儲存中的資料也會被同步, 因此,我們需要將現在的$scope.todos宣告刪掉:
1 |
$scope.todos = ['Item 1', 'Item 2', 'Item 3']; |
替換成下面的程式碼:
1 2 3 4 5 |
var todosInStore = localStorageService.get('todos'); $scope.todos = todosInStore && todosInStore.split('\n') || []; $scope.$watch('todos', function () { localStorageService.add('todos', $scope.todos.join('\n')); }, true); |
現在我們的控制器是這樣的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
'use strict'; angular.module('mytodoApp') .controller('MainCtrl', function ($scope, localStorageService) { var todosInStore = localStorageService.get('todos'); $scope.todos = todosInStore && todosInStore.split('\n') || []; $scope.$watch('todos', function () { localStorageService.add('todos', $scope.todos.join('\n')); }, true); $scope.addTodo = function () { $scope.todos.push($scope.todo); $scope.todo = ''; }; $scope.removeTodo = function (index) { $scope.todos.splice(index, 1); }; }); |
如果現在你在瀏覽器中檢視應用,你會發現Todo列表中沒有任何東西。因為這個應用從本地儲存中讀取了todo陣列,而本地儲存中還沒有任何Todo事項。
再來新增一些專案到列表中吧:
在當我們再次重新整理我們的瀏覽器的時候,這些專案都還在。萬歲!
用Chrome開發工具(Chrome DevTools)中的Resources皮膚裡確認我們的資料是不是真的被永久儲存在本地儲存中。在資源皮膚的左側裡選中’Local Storage’:
沒花多少時間,你就用Yeoman實現了一個很酷的Todo應用,快表揚下自己吧!現在你還能想到別的方式進行前端開發嗎?
來回顧一下,在這一節中我們做了:
- 用yo搭建了一個應用的模板檔案
- 為了增添應用的功能,用bower安裝應用需要的依賴
- 用grunt serve搭建和預覽我們的應用,所有的改動都能實時地反映在頁面上,不需要手動重新整理。
準備好上線產品吧
現在我們有了一個功能性的應用,現在來測試一下,實現一個可上線的版本。
- 使用Karma和Jasmine測試Karma是個一個JS測試框架。Angular生成器本身已經包括了兩個測試框架:ngScenario和Jasmine。當之前我們執行yo angular的時候,在mytodo資料夾下會生成了一個test目錄,還有一個karma.conf檔案,它會被放入在Node模組中以使用Karma。我們將會編輯一個Jasmine指令碼來完成我們的測試。現在先來看看要怎麼進行測試。現在回到命令列結束grunt server的程式(Ctrl+c)。在Gruntfile.js中已經有了用於執行測試的grunt任務,可以直接像下面這樣執行:
12345678910111213141516files: ['app/bower_components/jquery/jquery.js','app/bower_components/jquery-ui/ui/jquery-ui.js','app/bower_components/angular/angular.js','app/bower_components/angular-ui-sortable/sortable.js','app/bower_components/angular-mocks/angular-mocks.js','app/bower_components/angular-local-storage/angular-local-storage.js','app/scripts/*.js','app/scripts/**/*.js','test/mock/**/*.js','test/spec/**/*.js','app/bower_components/angular-resource/angular-resource.js','app/bower_components/angular-cookies/angular-cookies.js','app/bower_components/angular-sanitize/angular-sanitize.js','app/bower_components/angular-route/angular-route.js'],
還記得之前我們已經更改過main.js將資料從本地儲存中載入到頁面上麼?現在為了讓問題簡單一點,我們不再使用本地儲存而是用已經寫在陣列中的資料來做單元測試。將main.js中的內容替換成下面程式碼:
12345678910111213141516'use strict';angular.module('mytodoApp').controller('MainCtrl', function ($scope) {$scope.todos = [];$scope.addTodo = function () {$scope.todos.push($scope.todo);$scope.todo = '';};$scope.removeTodo = function (index) {$scope.todos.splice(index, 1);};});
下一步,修改在main.js中的單元測試。開啟test/spec/controllers/main.js。將下面的程式碼刪除:
123it('should attach a list of awesomeThings to the scope', function () {expect(scope.awesomeThings.length).toBe(3);});
替換成下面的程式碼:
123it('should have no items to start', function () {expect(scope.todos.length).toBe(0);});
再次執行grunt test,應該可以看到測試通過:讓我們再新增兩個用於測試新增和移除Todo事項的測試:
123456789101112it('should add items to the list', function () {scope.todo = 'Test 1';scope.addTodo();expect(scope.todos.length).toBe(1);});it('should add items to the list', function () {scope.todo = 'Test 1';scope.addTodo();scope.removeTodo(0);expect(scope.todos.length).toBe(0);});現在整個測試指令碼(test/spec/controllers/main.js)應該是像下面這樣:
123456789101112131415161718192021222324252627282930313233343536'use strict';describe('Controller: MainCtrl', function () {// load the controller's modulebeforeEach(module('mytodoApp'));var MainCtrl,scope;// Initialize the controller and a mock scopebeforeEach(inject(function ($controller, $rootScope) {scope = $rootScope.$new();MainCtrl = $controller('MainCtrl', {$scope: scope});}));it('should have no items to start', function () {expect(scope.todos.length).toBe(0);});it('should add items to the list', function () {scope.todo = 'Test 1';scope.addTodo();expect(scope.todos.length).toBe(1);});it('should add items to the list', function () {scope.todo = 'Test 1';scope.addTodo();scope.removeTodo(0);expect(scope.todos.length).toBe(0);});});再次執行測試,我們應該看到這3個測試都能通過:
棒極了!當你的應用越來越大、參與的開發者越來越多了以後,寫單元測試可以方便開發者查bug。在Yeoman的幫助下,編寫單元測試變得很輕鬆,你再也沒有理由不寫測試啦!;)
- 優化
準備釋出你的Todo應用了嗎?現在給我們的應用建立一個上線版本吧。我們需要規範程式碼、跑測試、壓縮JS和CSS程式碼(減少網路請求)、優化圖片還有編譯使用了預處理的程式碼,讓我們的應用盡可能的精簡。
非常神奇的是,上面的工作我們都能通過下面這行命令實現:
1 |
$ grunt |
這行命令會遍歷Yeoman生成的Grunt任務和配置,然後建立出應用的過渡版本。過一會命令列中會顯示出應用的結構和生成這個應用總共花費的時間,以及完成每一項任務所花費的時間。
現在你的應用應該很不錯了。可以在’mytodo’中的’dist’資料夾下找到這個應用。如果想要在本地檢視這個應用的話,執行下面這行命令:
1 |
$ grunt serve:dist |
它會生成你的專案並且啟動本地的伺服器。
Yeoman可以做的更多
除了在這個教程中我們使用到的框架外,Yeoman還支援很多其他的框架。 舉例來說,Anugular生成器也支援建立新的檢視、指令和控制器。可以通過執行yo angular:controller controllerName搭建一個新的控制器,同時在app.js中的路由也會被更新。在可能使用單元測試的地方,我們也會試圖搭建測試。
想要知道更多有關於Angular生成器的Yeoman命令,請檢視generator readme。
下一步是什麼
- Grunt提供了幾乎所有與你的應用互動的服務,包括編譯CoffeeScript或者與像PHP或者Express編寫的中間層連線。你的Yeoman已經生成了Gruntfile.js,所以你要做的就是看一看如何配置Grunt tasks。
- Bower上可以獲取的package的資源越來越多,可以在這裡檢視所有的packages。
- Yeoman會持續更新。檢視yeoman.io獲取更多資訊,也可以關注@yeoman和+Yeoman。
以上由“戴帽子的人”釋出。謝謝!