AngularJS入門

AngularJS入門教程目錄
1. 快速開始
2. 導言和準備
3. 引導程序
4. 靜態模板
5. AngularJS 模板
6. 叠代器過濾
7. 雙向綁定
8. XHR 和依賴註入
9. 鏈接與圖片模板
10.路由與多視圖
11.更多模板
12.過濾器
13.事件處理器
14.REST 和定制服務
15.完結篇

AngularJS快速開始
Hello World!
開始學習AngularJS的一個好方法是創建經典應用程序“Hello World!”:
1. 使用您喜愛的文本編輯器,創建一個HTML 文件,例如:helloworld.html。
2. 將下面的源代碼複制到您的HTML 文件。
3. 在web 瀏覽器中打開這個HTML 文件。
源代碼
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/angular-1.0.1.min.js"></script>
</head>
<body>
        Hello {{'World'}}!
</body>
</html>
請在您的瀏覽器中運行以上代碼查看效果。
現在讓我們仔細看看代碼,看看到底怎麽回事。 當加載該頁時,標記ng-app告訴AngularJS處理整個HTML頁並引導應用:
<html ng-app>
這行載入AngularJS腳本:
<script src="http://code.angularjs.org/angular-1.0.1.min.js"></script>
(想了解AngularJS處理整個HTML頁的細節,請看Bootstrap。)
最後,標簽中的正文是應用的模板,在UI中顯示我們的問候語:
Hello {{'World'}}!
註意,使用雙大括號標記{{}}的內容是問候語中綁定的表達式,這個表達式是一個簡單的字符串‘World’。
下面,讓我們看一個更有趣的例子:使用AngularJS對我們的問候語文本綁定一個動態表達式。
Hello AngularJS World!
本示例演示AngularJS的雙向數據綁定(bi-directional data binding):
1. 編輯前面創建的helloworld.html 文檔。
2. 將下面的源代碼複制到您的HTML 文件。
3. 刷新瀏覽器窗口。
源代碼


<!doctype html>
<html ng-app>
<head>
    <script src="http://code.angularjs.org/angular-1.0.1.min.js"></script>
</head>
<body>
    Your name: <input type="text" ng-model="yourname" placeholder="World">
    <hr>
    Hello {{yourname || 'World'}}!
</body>
</html>
請在您的瀏覽器中運行以上代碼查看效果。
該示例有一下幾點重要的註意事項:
 文本輸入指令<input ng-model="yourname" />綁定到一個叫yourname 的模型變量。
 雙大括號標記將yourname 模型變量添加到問候語文本。
 你不需要為該應用另外註冊一個事件偵聽器或添加事件處理程序!
現在試著在輸入框中鍵入您的名稱,您鍵入的名稱將立即更新顯示在問候語中。 這就是AngularJS雙向數據綁定的概
念。 輸入框的任何更改會立即反映到模型變量(一個方向),模型變量的任何更改都會立即反映到問候語文本中
(另一方向)。
AngularJS應用的解析
本節描述AngularJS應用程序的三個組成部分,並解釋它們如何映射到模型-視圖-控制器設計模式:
模板(Templates)
模板是您用HTML和CSS編寫的文件,展現應用的視圖。 您可給HTML添加新的元素、屬性標記,作為AngularJS編譯器
的指令。 AngularJS編譯器是完全可擴展的,這意味著通過AngularJS您可以在HTML中構建您自己的HTML標記!
應用程序邏輯(Logic)和行為(Behavior)
應用程序邏輯和行為是您用JavaScript定義的控制器。AngularJS與標準AJAX應用程序不同,您不需要另外編寫偵聽
器或DOM控制器,因為它們已經內置到AngularJS中了。這些功能使您的應用程序邏輯很容易編寫、測試、維護和理解。
模型數據(Data)
模型是從AngularJS作用域對象的屬性引申的。模型中的數據可能是Javascript對象、數組或基本類型,這都不重要,
重要的是,他們都屬於AngularJS作用域對象。
AngularJS通過作用域來保持數據模型與視圖界面UI的雙向同步。一旦模型狀態發生改變,AngularJS會立即刷新反映
在視圖界面中,反之亦然。
此外,AngularJS還提供了一些非常有用的服務特性:
1. 底層服務包括依賴註入,XHR、緩存、URL 路由和瀏覽器抽象服務。
2. 您還可以擴展和添加自己特定的應用服務。
3. 這些服務可以讓您非常方便的編寫WEB 應用。
AngularJS 入門教程



AngularJS入門教程:導言和準備
學習AngularJS的一個好方法是逐步完成本教程,它將引導您構建一個完整的AngularJS web應用程序。 該web應用是
一個Android設備清單的目錄列表,您可以篩選列表以便查看您感興趣的設備,然後查看設備的詳細信息。
本教程將向您展示AngularJS怎樣使得web應用更智能更靈活,而且不需要各種擴展程序或插件。 通過本教程的學習,
您將:
1. 閱讀示例學習怎樣使用AngularJS 的客戶端數據綁定和依賴註入功能來建立可立即響應用戶操
作的動態數據視圖。
2. 學習如何使用AngularJS 創建數據偵聽器,且不需要進行DOM 操作。
3. 學習一種更好、更簡單的方法來測試您的web 應用程序。
4. 學習如何使用AngularJS 創建常見的web 任務,例如更方便的將數據引入應用程序。
而且這一切可在任何一個瀏覽器實現,無需配置瀏覽器!
當你完成了本教程後,您將學會:
1. 創建一個可在任何瀏覽器中的工作的動態應用。
2. 了解AngularJS 與其它JavaScript 框架之間的區別。
3. 了解AngularJS 如何實現數據綁定。
4. 利用AngularJS 的種子項目快速創建自己的項目。
5. 創建和運行測試。
6. 學習更多AngularJS 標識資源(API)。
AngularJS 入門教程

本教程將指導您完成一個簡單的應用程序創建過程,包括編寫和運行單元測試、不斷地測試應用。 教程的每個步驟
為您提供建議以了解更多有關AngularJS和您創建的web應用程序。 您可能會在短時間內快速讀完本教程,也可能需
要花大量時間深入研究本教程。 如果想看一個簡短的AngularJS介紹文檔,請查看[快速開始][ Getting Started]文
檔。
搭建學習環境
無論是Mac、Linux或Windows環境中,您均可遵循本教程學習編程。您可以使用源代碼管理版本控制系統Git獲取本教
程項目的源代碼文件,或直接從網上下載本教程項目源代碼文件的鏡像歸檔壓縮包。
1. 您需要安裝Node.js和Testacular來運行本項目,請到Node.js官方網站下載並安裝最新版,然後把node可執
行程序路徑添加到系統環境變量PATH中,完成後在命令行中運行一下命令可以查看是否安裝成功:
2. node -version
然後安裝Testacular單元測試程序,請運行如下命令:
npm install -g testacular
3. 安裝Git工具,然後用以下命令從Github複制本教程項目的源代碼文件:
4. git clone git://github.com/angular/angular-phonecat.git
您也可以直接從網上下載本教程項目源代碼的鏡像歸檔壓縮包。這個命令會在您當前文件夾中建立新文件夾angular-phon
ecat。
5. 最後一件事要做的就是確保您的計算機安裝了web瀏覽器和文本編輯器。
6. 進入教程源代碼文件包angular-phonecat,運行服務器後臺程序,開始學習AngularJS!
7. cd angular-phonecat
8. node scripts/web-server.js


AngularJS入門教程00:引導程序
我們現在開始準備編寫AngularJS應用——phonecat。這一步驟(步驟0),您將會熟悉重要的源代碼文件,學習啟動
包含AngularJS種子項目的開發環境,並在瀏覽器端運行應用。
1. 進入angular-phonecat目錄,運行如下命令:
2. git checkout -f step-0
該命令將重置phonecat項目的工作目錄,建議您在每一學習步驟運行此命令,將命令中的數字改成您學習步驟對應的
數字,該命令將清除您在工作目錄內做的任何更改。
3. 運行以下命令:
4. node scripts/web-server.js
來啟動服務器,啟動後命令行終端將會提示Http Server running at http://localhost:8000,請不要關閉該終端,關閉該終端即
關閉了服務器。在瀏覽器中輸入http://localhost:8000/app/index.html來訪問我們的phonecat應用。
現在,在瀏覽器中您應該已經看到了我們的初始應用,很簡單,但說明我們的項目已經可以運行了。
應用中顯示的“Nothing here yet!”是由如下HTML代碼構建而成,代碼中包含了AngularJS的關鍵元素,正是我們需
要學習的。
app/index.html
<!doctype html>
<html lang="en" ng-app>
<head>
<meta charset="utf-8">
<title>My HTML File</title>
    <link rel="stylesheet" href="css/app.css">
    <link rel="stylesheet" href="css/bootstrap.css">
<script src="lib/angular/angular.js"></script>
</head>
<body>
    <p>Nothing here {{'yet' + '!'}}</p>
</body>
</html>
代碼在做什麽呢?
ng-app指令:
<html lang="en" ng-app>
ng-app指令標記了AngularJS腳本的作用域,在<html>中添加ng-app屬性即說明整個<html>都是AngularJS腳本作用域。開發
者也可以在局部使用ng-app指令,如<div ng-app>,則AngularJS腳本僅在該<div>中運行。
AngularJS腳本標簽:
<script src="lib/angular/angular.js"></script>
這行代碼載入angular.js腳本,當瀏覽器將整個HTML頁面載入完畢後將會執行該angular.js腳本,angular.js腳本運
行後將會尋找含有ng-app指令的HTML標簽,該標簽即定義了AngularJS應用的作用域。
雙大括號綁定的表達式:
AngularJS 入門教程

<p>Nothing here {{'yet' + '!'}}</p>
這行代碼演示了AngularJS模板的核心功能——綁定,這個綁定由雙大括號{{}}和表達式'yet' + '!'組成。
這個綁定告訴AngularJS需要運算其中的表達式並將結果插入DOM中,接下來的步驟我們將看到,DOM可以隨著表達式
運算結果的改變而實時更新。
AngularJS表達式Angular expression是一種類似於JavaScript的代碼片段,AngularJS表達式僅在AngularJS的作用
域中運行,而不是在整個DOM中運行。
引導AngularJS應用
通過ngApp指令來自動引導AngularJS應用是一種簡潔的方式,適合大多數情況。在高級開發中,例如使用腳本裝載應
用,您也可以使用bootstrap手動引導AngularJS應用。
AngularJS應用引導過程有3個重要點:
1. 註入器(injector)將用於創建此應用程序的依賴註入(dependency injection);
2. 註入器將會創建根作用域作為我們應用模型的範圍;
3. AngularJS 將會鏈接根作用域中的DOM,從用ngApp 標記的HTML 標簽開始,逐步處理DOM 中指
令和綁定。
一旦AngularJS應用引導完畢,它將繼續偵聽瀏覽器的HTML觸發事件,如鼠標點擊事件、按鍵事件、HTTP傳入響應等
改變DOM模型的事件。這類事件一旦發生,AngularJS將會自動檢測變化,並作出相應的處理及更新。
上面這個應用的結構非常簡單。該模板包僅含一個指令和一個靜態綁定,其中的模型也是空的。下一步我們嘗試稍複
雜的應用!
我工作目錄中這些文件是幹什麽的?
上面的應用來自於AngularJS種子項目,我們通常可以使用AngularJS種子項目來創建新項目。種子項目包括最新的An
gularJS代碼庫、測試庫、腳本和一個簡單的應用程序示例,它包含了開發一個典型的web應用程序所需的基本配置。


對於本教程,我們對AngularJS種子項目進行了下列更改:
1. 刪除示例應用程序;
2. 添加手機圖像到app/img/phones/;
3. 添加手機數據文件(JSON)到app/phones/;
4. 添加Twitter Bootstrap 文件到app/css/ 和app/img/。
練習
試試把關於數學運算的新表達式添加到index.html:
<p>1 + 2 = {{ 1 + 2 }}</p>
總結
現在讓我們轉到步驟1,將一些內容添加到web應用程序。
AngularJS 入門教程

AngularJS入門教程01:靜態模板
為了說明angularJS如何增強了標準HTML,我們先將創建一個靜態HTML頁面模板,然後把這個靜態HTML頁面模板轉換
成能動態顯示的AngularJS模板。
在本步驟中,我們往HTML頁面中添加兩個手機的基本信息,用以下命令將工作目錄重置到步驟1。
git checkout -f step-1
請編輯app/index.html文件,將下面的代碼添加到index.html文件中,然後運行該應用查看效果。
app/index.html
<ul>
    <li>
        <span>Nexus S</span>
        <p>
        Fast just got faster with Nexus S.
        </p>
    </li>
    <li>
        <span>Motorola XOOM™ with Wi-Fi</span>
        <p>
        The Next, Next Generation tablet.
        </p>
    </li>
</ul>
練習
嘗試添加多個靜態HTML代碼到index.html, 例如:
<p>Total number of phones: 2</p>
總結
本步驟往應用中添加了靜態HTML手機列表, 現在讓我們轉到步驟2以了解如何使用AngularJS動態生成相同的列表。


AngularJS入門教程02:AngularJS 模板
是時候給這些網頁來點動態特性了——用AngularJS!我們這里為後面要加入的控制器添加了一個測試。
一個應用的代碼架構有很多種。對於AngularJS應用,我們鼓勵使用模型-視圖-控制器(MVC)模式解耦代碼和分離關
註點。考慮到這一點,我們用AngularJS來為我們的應用添加一些模型、視圖和控制器。
請重置工作目錄:
git checkout -f step-2
我們的應用現在有了一個包含三部手機的列表。
步驟1和步驟2之間最重要的不同在下面列出。,你可以到GitHub去看完整的差別。
視圖和模板
在AngularJS中,一個視圖是模型通過HTML**模板**渲染之後的映射。這意味著,不論模型什麽時候發生變化,Angul
arJS會實時更新結合點,隨之更新視圖。
比如,視圖組件被AngularJS用下面這個模板構建出來:
<html ng-app>
<head>
...
<script src="lib/angular/angular.js"></script>
<script src="js/controllers.js"></script>
</head>
<body ng-controller="PhoneListCtrl">
    <ul>
        <li ng-repeat="phone in phones">
        {{phone.name}}
        <p>{{phone.snippet}}</p>
        </li>
    </ul>
</body>
</html>
我們剛剛把靜態編碼的手機列表替換掉了,因為這里我們使用ngRepeat指令和兩個用花括號包裹起來的AngularJS表
達式——{{phone.name}}和{{phone.snippet}}——能達到同樣的效果。
 在<li>標簽里面的ng-repeat="phone in phones"語句是一個AngularJS 叠代器。這個叠代器告訴
AngularJS 用第一個<li>標簽作為模板為列表中的每一部手機創建一個<li>元素。
 正如我們在第0 步時學到的,包裹在phone.name 和phone.snippet 周圍的花括號標識著數據綁定。和常
量計算不同的是,這里的表達式實際上是我們應用的一個數據模型引用,這些我們在PhoneListCtrl
控制器里面都設置好了。
AngularJS 入門教程

模型和控制器
在PhoneListCtrl控制器里面初始化了數據模型(這里只不過是一個包含了數組的函數,數組中存儲的對象是手機數據列
表):
app/js/controller.js:
function PhoneListCtrl($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——控制器方法的名字(在JS 文件controllers.js 中)和<body>標簽里面的ngController
指令的值相匹配。
 手機的數據此時與註入到我們控制器函數的作用域($scope)相關聯。當應用啟動之後,會有一
個根作用域被創建出來,而控制器的作用域是根作用域的一個典型後繼。這個控制器的作用域
對所有<body ng-controller="PhoneListCtrl">標記內部的數據綁定有效。
AngularJS的作用域理論非常重要:一個作用域可以視作模板、模型和控制器協同工作的粘接器。AngularJS使用作用
域,同時還有模板中的信息,數據模型和控制器。這些可以幫助模型和視圖分離,但是他們兩者確實是同步的!任何
對於模型的更改都會即時反映在視圖上;任何在視圖上的更改都會被立刻體現在模型中。


想要更加深入理解AngularJS的作用域,請參看AngularJS作用域文檔。
測試
“AngularJS方式”讓開發時代碼測試變得十分簡單。讓我們來瞅一眼下面這個為控制器新添加的單元測試:
test/unit/controllersSpec.js:
describe('PhoneCat controllers', function() {
    describe('PhoneListCtrl', function(){
        it('should create "phones" model with 3 phones', function() {
        var scope = {},
        ctrl = new PhoneListCtrl(scope);
        expect(scope.phones.length).toBe(3);
        });
    });
});
這個測試驗證了我們的手機數組里面有三條記錄(暫時無需弄明白這個測試腳本)。這個例子顯示出為AngularJS的
代碼創建一個單元測試是多麽的容易。正因為測試在軟件開發中是必不可少的環節,所以我們使得在AngularJS可以
輕易地構建測試,來鼓勵開發者多寫它們。
在寫測試的時候,AngularJS的開發者傾向於使用Jasmine行為驅動開發(BBD)框架中的語法。盡管AngularJS沒有強
迫你使用Jasmine,但是我們在教程里面所有的測試都使用Jasmine編寫。你可以在Jasmine的官方主頁或者Jasmine W
iki上獲得相關知識。
基於AngularJS的項目被預先配置為使用JsTestDriver來運行單元測試。你可以像下面這樣運行測試:
1. 在一個單獨的終端上,進入到angular-phonecat 目錄並且運行./scripts/test-server.sh 來啟動測試
(Windows 命令行下請輸入.\scripts\test-server.bat 來運行腳本,後面腳本命令運行方式類似);
2. 打開一個新的瀏覽器窗口,並且轉到http://localhost:9876 ;
3. 選擇“Capture this browser in strict mode”。
這個時候,你可以拋開你的窗口不管然後把這事忘了。JsTestDriver會自己把測試跑完並且把結果輸出在你的終端里。
4. 運行./scripts/test.sh進行測試 。
你應當看到類似於如下的結果:
Chrome: Runner reset.
.
Total 1 tests (Passed: 1; Fails: 0; Errors: 0) (2.00 ms)
Chrome 19.0.1084.36 Mac OS: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (2.00 ms)
耶!測試通過了!或者沒有... 註意:如果在你運行測試之後發生了錯誤,關閉瀏覽器然後回到終端關了腳本,然後
在重新來一邊上面的步驟。
練習
 為index.html添加另一個數據綁定。例如:
 <p>Total number of phones: {{phones.length}}</p>
 創建一個新的數據模型屬性,並且把它綁定到模板上。例如:
 $scope.hello = "Hello, World!"
AngularJS 入門教程

更新你的瀏覽器,確保顯示出來“Hello, World!”
 用一個叠代器創建一個簡單的表:
 <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>
現在讓數據模型表達式的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>
 確定把toBe(3)改成toBe(4)之後單元測試失敗,然後重新跑一遍./scripts/test.sh腳本
總結
你現在擁有一個模型,視圖,控制器分離的動態應用了,並且你隨時進行了測試。現在,你可以進入到步驟3來為應
用加入全文檢索功能了。


AngularJS入門教程03:叠代器過濾
我們在上一步做了很多基礎性的訓練,所以現在我們可以來做一些簡單的事情嘍。我們要加入全文檢索功能(沒錯,
這個真的非常簡單!)。同時,我們也會寫一個端到端測試,因為一個好的端到端測試可以幫上很大忙。它監視著你
的應用,並且在發生回歸的時候迅速報告。
請重置工作目錄:
git checkout -f step-3
我們的應用現在有了一個搜索框。註意到頁面上的手機列表隨著用戶在搜索框中的輸入而變化。
步驟2和步驟3之間最重要的不同在下面列出。你可以在GitHub里看到完整的差別。
控制器
我們對控制器不做任何修改。
模板
app/index.html
<div class="container-fluid">
<div class="row-fluid">
    <div class="span2">
    <!--Sidebar content-->
    Search: <input ng-model="query">
        </div>
        <div class="span10">
        <!--Body content-->
            <ul class="phones">
                <li ng-repeat="phone in phones | filter:query">
                {{phone.name}}
                <p>{{phone.snippet}}</p>
                </li>
            </ul>
        </div>
    </div>
</div>
我們現在添加了一個<input>標簽,並且使用AngularJS的$filter函數來處理ngRepeat指令的輸入。
這樣允許用戶輸入一個搜索條件,立刻就能看到對電話列表的搜索結果。我們來解釋一下新的代碼:
 數據綁定: 這是AngularJS的一個核心特性。當頁面加載的時候,AngularJS會根據輸入框的屬性值名字,將
其與數據模型中相同名字的變量綁定在一起,以確保兩者的同步性。
在這段代碼中,用戶在輸入框中輸入的數據名字稱作query,會立刻作為列表叠代器(phone in phones | filter:query`)其
過濾器的輸入。當數據模型引起叠代器輸入變化的時候,叠代器可以高效得更新DOM將數據模型最新的狀態反映出來。
AngularJS 入門教程

 使用filter過濾器:filter函數使用query的值來創建一個只包含匹配query記錄的新數組。
ngRepeat會根據filter過濾器生成的手機記錄數據數組來自動更新視圖。整個過程對於開發者來說都是透明的。
測試
在步驟2,我們學習了編寫和運行一個測試的方法。單元測試用來測試我們用js編寫的控制器和其他組件都非常方便,
但是不能方便的對DOM操作和應用集成進行測試。對於這些來說,端到端測試是一個更好的選擇。
搜索特性是完全通過模板和數據綁定實現的,所以我們的第一個端到端測試就來驗證這些特性是否符合我們的預期。
test/e2e/scenarios.js:
describe('PhoneCat App', function() {
    describe('Phone list view', function() {
    beforeEach(function() {
        browser().navigateTo('../../app/index.html');
        });
        it('should filter the phone list as user types into the search box', function() {
        expect(repeater('.phones li').count()).toBe(3);
        input('query').enter('nexus');
        expect(repeater('.phones li').count()).toBe(1);
        input('query').enter('motorola');
        expect(repeater('.phones li').count()).toBe(2);
        });
    });
});


盡管這段測試代碼的語法看起來和我們之前用Jasmine寫的單元測試非常像,但是端到端測試使用的是AngularJS端到
端測試器提供的接口。
運行一個端到端測試,在瀏覽器新標簽頁中打開下面任意一個:
 node.js 用戶:http://localhost:8000/test/e2e/runner.html
 使用其他http 服務器的用戶:http://localhost:[port-number]/[context-path]/test/e2e/runner.html
 訪客:http://angular.github.com/angular-phonecat/step-3/test/e2e/runner.html
這個測試驗證了搜素框和叠代器被正確地集成起來。你可以發現,在AngularJS里寫一個端到端測試多麽的簡單。盡
管這個例子僅僅是一個簡單的測試,但是用它來構建任何一個複雜、可讀的端到端測試都很容易。
練習
 在index.html 模板中添加一個{{query}}綁定來實時顯示query 模型的當前值,然後觀察他們是如何根
據輸入框中的值而變化。
 現在我們來看一下我們怎麽讓query模型的值出現在HTML的頁面標題上。
你或許認為像下面這樣在title標簽上加上一個綁定就行了:
<title>Google Phone Gallery: {{query}}</title>
但是,當你重載頁面的時候,你根本沒辦法得到期望的結果。這是因為query模型僅僅在body元素定義的作用域內才有效。
<body ng-controller="PhoneListCtrl">
如果你想讓<title>元素綁定上query模型,你必須把ngController聲明移動到HTML元素上,因為它是title和body元素的共同祖先。
<html ng-app ng-controller="PhoneListCtrl">
一定要註意把body元素上的ng-controller聲明給刪了。
當綁定兩個花括號在title元素上可以實現我們的目標,但是你或許發現了,頁面正加載的時候它們已經顯示給用戶看
了。一個更好的解決方案是使用ngBind或者ngBindTemplate指令,它們在頁面加載時對用戶是不可見的:
<title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>
 在test/e2e/scenarios.js的describe塊中加入下面這些端到端測試代碼:
 it('should display the current filter value within an element with id "status"',
 function() {
 expect(element('#status').text()).toMatch(/Current filter: \s*$/);
 input('query').enter('nexus');
 expect(element('#status').text()).toMatch(/Current filter: nexus\s*$/);
 //alternative version of the last assertion that tests just the value of the binding
 using('#status').expect(binding('query')).toBe('nexus');
 });
刷新瀏覽器,端到端測試器會報告測試失敗。為了讓測試通過,編輯index.html,添加一個id為“status”的div或者p元素,
內容是一個query綁定,再加上Current filter:前綴。例如:
<div id="status">Current filter: {{query}}</div>
AngularJS 入門教程

 在端到端測試里面加一條pause();語句,重新跑一遍。你將發現測試器暫停了!這樣允許你有機會在測試運行過
程中查看你應用的狀態。測試應用是實時的!你可以更換搜索內容來證明。稍有經驗你就會知道,這對於在
端到端測試中迅速找到問題是多麽的關鍵。
總結
我們現在添加了全文搜索功能,並且完成一個測試證明了搜索是對的!現在讓我們繼續到步驟4來看看給我們的手機
應用增加排序功能。


AngularJS入門教程04:雙向綁定
在這一步你會增加一個讓用戶控制手機列表顯示順序的特性。動態排序可以這樣實現,添加一個新的模型屬性,把它
和叠代器集成起來,然後讓數據綁定完成剩下的事情。
請重置工作目錄:
git checkout -f step-4
你應該發現除了搜索框之外,你的應用多了一個下來菜單,它可以允許控制電話排列的順序。
步驟3和步驟4之間最重要的不同在下面列出。你可以在GitHub里看到完整的差別。
模板
app/index.html
Search: <input ng-model="query">
Sort by:
<select ng-model="orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
<ul class="phones">
    <li ng-repeat="phone in phones | filter:query | orderBy:orderProp">
    {{phone.name}}
    <p>{{phone.snippet}}</p>
    </li>
</ul>
我們在index.html中做了如下更改:
 首先,我們增加了一個叫做orderProp 的<select>標簽,這樣我們的用戶就可以選擇我們提供的兩種
排序方法。
AngularJS 入門教程

 然後,在filter 過濾器後面添加一個orderBy 過濾器用其來處理進入叠代器的數據。orderBy 過濾
器以一個數組作為輸入,複制一份副本,然後把副本重排序再輸出到叠代器。
AngularJS在select元素和orderProp模型之間創建了一個雙向綁定。而後,orderProp會被用作orderBy過濾器的輸入。
正如我們在步驟3中討論數據綁定和叠代器的時候所說的一樣,無論什麽時候數據模型發生了改變(比如用戶在下拉
菜單中選了不同的順序),AngularJS的數據綁定會讓視圖自動更新。沒有任何笨拙的DOM操作!
控制器
app/js/controllers.js:
function PhoneListCtrl($scope) {
    $scope.phones = [
    {"name": "Nexus S",
    "snippet": "Fast just got faster with Nexus S.",
    "age": 0},
    {"name": "Motorola XOOM™ with Wi-Fi",
    "snippet": "The Next, Next Generation tablet.",
    "age": 1},
    {"name": "MOTOROLA XOOM™",
    "snippet": "The Next, Next Generation tablet.",
    "age": 2}
    ];
    $scope.orderProp = 'age';
}
 我們修改了phones 模型—— 手機的數組 ——為每一個手機記錄其增加了一個age 屬性。我們會
根據age 屬性來對手機進行排序。
 我們在控制器代碼里加了一行讓orderProp的默認值為age。如果我們不設置默認值,這個模型會在我們的用戶在
下拉菜單選擇一個順序之前一直處於未初始化狀態。


現在我們該好好談談雙向數據綁定了。註意到當應用在瀏覽器中加載時,“Newest”在下拉菜單中被選中。這是因為
我們在控制器中把orderProp設置成了‘age’。所以綁定在從我們模型到用戶界面的方向上起作用——即數據從模型到
視圖的綁定。現在當你在下拉菜單中選擇“Alphabetically”,數據模型會被同時更新,並且手機列表數組會被重新
排序。這個時候數據綁定從另一個方向產生了作用——即數據從視圖到模型的綁定。
測試
我們所做的更改可以通過一個單元測試或者一個端到端測試來驗證正確性。我們首先來看看單元測試:
test/unit/controllersSpec.js:
describe('PhoneCat controllers', function() {
    describe('PhoneListCtrl', function(){
        var scope, ctrl;
        beforeEach(function() {
            scope = {},
            ctrl = new PhoneListCtrl(scope);
        });
            it('should create "phones" model with 3 phones', function() {
            expect(scope.phones.length).toBe(3);
        });
            it('should set the default value of orderProp model', function() {
            expect(scope.orderProp).toBe('age');
        });
    });
});
單元測試現在驗證了默認值被正確設置。
我們使用Jasmine的接口把PhoneListCtrl控制器提取到一個beforeEach塊中,這個塊會被所有的父塊describe中的所有測試所
共享。
運行這些單元測試,跟以前一樣,執行./scripts/test.sh腳本,你應該會看到如下輸出(註意:要在瀏覽器打開http://l
ocalhost:9876並進入嚴格模式,測試才會運行!):
Chrome: Runner reset.
..
Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (3.00 ms)
Chrome 19.0.1084.36 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms)
現在我們把註意力轉移到端到端測試上來。
test/e2e/scenarios.js:
...
it('should be possible to control phone order via the drop down select box',
function() {
    //let's narrow the dataset to make the test assertions shorter
    input('query').enter('tablet');
    expect(repeater('.phones li', 'Phone List').column('phone.name')).
    toEqual(["Motorola XOOM\u2122 with Wi-Fi",
    "MOTOROLA XOOM\u2122"]);
    select('orderProp').option('Alphabetical');
    expect(repeater('.phones li', 'Phone List').column('phone.name')).
    toEqual(["MOTOROLA XOOM\u2122",
    "Motorola XOOM\u2122 with Wi-Fi"]);
});
...
AngularJS 入門教程

端到端測試驗證了選項框的排序機制是正確的。
你現在可以刷新你的瀏覽器,然後重新跑一遍端到端測試,或者你可以在AngularJS的服務器上運行一下。
練習
 在PhoneListCtrl 控制器中,把設置orderProp 那條語句刪掉,你會看到AngularJS 會在下拉菜單中臨
時添加一個空白的選項,並且排序順序是默認排序(即未排序)。
 在index.html 模板里面添加一個`{{orderProp}}綁定來實時顯示它的值。
總結
現在你已經為你的應用提供了搜索功能,並且完整的進行了測試。步驟5我們將學習AngularJS的服務以及AngularJS
如何使用依賴註入。


AngularJS入門教程05:XHR和依賴註入
到現在為止,我們使用是硬編碼的三條手機記錄數據集。現在我們使用AngularJS一個內置服務$http來獲取一個更大
的手機記錄數據集。我們將使用AngularJS的依賴註入(dependency injection (DI))功能來為PhoneListCtrl控制器提
供這個AngularJS服務。
請重置工作目錄:
git checkout -f step-5
刷新瀏覽器,你現在應該能看到一個20部手機的列表。
步驟4和步驟5之間最重要的不同在下面列出。你可以在GitHub里看到完整的差別。
數據
你項目當中的app/phones/phones.json文件是一個數據集,它以JSON格式存儲了一張更大的手機列表。
下面是這個文件的一個樣例:
[
    {
        "age": 13,
        "id": "motorola-defy-with-motoblur",
        "name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
        "snippet": "Are you ready for everything life throws your way?"
        ...
    },
...
]
控制器
我們在控制器中使用AngularJS服務$http向你的Web服務器發起一個HTTP請求,以此從app/phones/phones.json文件中獲取
數據。$http僅僅是AngularJS眾多內建服務中之一,這些服務可以處理一些Web應用的通用操作。AngularJS能將這些服
務註入到任何你需要它們的地方。
服務是通過AngularJS的依賴註入DI子系統來管理的。依賴註入服務可以使你的Web應用良好構建(比如分離表現層、
數據和控制三者的部件)並且松耦合(一個部件自己不需要解決部件之間的依賴問題,它們都被DI子系統所處理)。
app/js/controllers.js
function PhoneListCtrl($scope, $http) {
    $http.get('phones/phones.json').success(function(data) {
            $scope.phones = data;
    });
        $scope.orderProp = 'age';
}
//PhoneListCtrl.$inject = ['$scope', '$http'];
$http向Web服務器發起一個HTTP GET請求,索取phone/phones.json(註意,url是相對於我們的index.html文件的)。服務器用js
on文件中的數據作為響應。(這個響應或許是實時從後端服務器動態產生的。但是對於瀏覽器來說,它們看起來都是
一樣的。為了簡單起見,我們在教程里面簡單地使用了一個json文件。)
AngularJS 入門教程

$http服務用success返回[對象應答][ng.$q]。當異步響應到達時,用這個對象應答函數來處理服務器響應的數據,並且
把數據賦值給作用域的phones數據模型。註意到AngularJS會自動檢測到這個json應答,並且已經為我們解析出來了!
為了使用AngularJS的服務,你只需要在控制器的構造函數里面作為參數聲明出所需服務的名字,就像這樣:
function PhoneListCtrl($scope, $http) {...}
當控制器構造的時候,AngularJS的依賴註入器會將這些服務註入到你的控制器中。當然,依賴註入器也會處理所需
服務可能存在的任何傳遞性依賴(一個服務通常會依賴於其他的服務)。
註意到參數名字非常重要,因為註入器會用他們去尋找相應的依賴。
'$'前綴命名習慣
你可以創建自己的服務,實際上我們在步驟11就會學習到它。作為一個命名習慣,AngularJS內建服務,作用域方法,
以及一些其他的AngularJS API都在名字前面使用一個‘$’前綴。不要使用‘$’前綴來命名你自己的服務和模型,
否則可能會產生名字沖突。
關於JS 壓縮
由於AngularJS是通過控制器構造函數的參數名字來推斷依賴服務名稱的。所以如果你要壓縮PhoneListCtrl控制器的JS代
碼,它所有的參數也同時會被壓縮,這時候依賴註入系統就不能正確的識別出服務了。
為了克服壓縮引起的問題,只要在控制器函數里面給$inject屬性賦值一個依賴服務標識符的數組,就像被註釋掉那段
最後一行那樣:
PhoneListCtrl.$inject = ['$scope', '$http'];
另一種方法也可以用來指定依賴列表並且避免壓縮問題——使用Javascript數組方式構造控制器:把要註入的服務放
到一個字符串數組(代表依賴的名字)里,數組最後一個元素是控制器的方法函數:


var PhoneListCtrl = ['$scope', '$http', function($scope, $http) { /* constructor body */ }];
上面提到的兩種方法都能和AngularJS可註入的任何函數完美協作,要選哪一種方式完全取決於你們項目的編程風格,
建議使用數組方式。
測試
test/unit/controllerSpec.js:
由於我們現在開始使用依賴註入,並且我們的控制器也含有了許多依賴服務,所以為我們的控制器構造測試就有一點
小小的複雜了。我們需要使用new操作並且提供給構造器包括$http的一些偽實現。然而,我們推薦的方法(而且更加簡
單噢)是在測試環境下創建一個控制器,使用的方法和AngularJS在產品代碼於下面的場景下做的一樣:
describe('PhoneCat controllers', function()     {   
    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});
            }));
        }
    }
註意:因為我們在測試環境中加載了Jasmine和angular-mock.js,我們有了兩個輔助方法,module和inject,來幫助
我們獲得和配置註入器。
用如下方法,我們在測試環境中創建一個控制器:
 我們使用inject方法將$rootScope,$controller和$httpBackend服務實例註入到Jasmine的beforeEach函數里。這
些實例都來自一個註入器,但是這個註入器在每一個測試內部都會被重新創建。這樣保證了每一個測試都從
一個周知的起始點開始,並且每一個測試都和其他測試相互獨立。
 調用$rootScope.$new()來為我們的控制器創建一個新的作用域。
 PhoneListCtrl函數和剛創建的作用域作為參數,傳遞給已註入的$controller函數。
由於我們現在的代碼在創建PhoneListCtrl子作用域之前,於控制器中使用$http服務獲取了手機列表數據,我們需要告訴
測試套件等待一個從控制器來的請求。我們可以這樣做:
 將請求服務$httpBackend註入到我們的beforeEach函數中。這是這個服務的一個偽版本,這樣做在產品環境中有助於
處理所有的XHR和JSONP請求。服務的偽版本允許你不用考慮原生API和全局狀態——隨便一個都能構成測試的
噩夢——就可以寫測試。
 使用$httpBackend.expectGET方法來告訴$httpBackend服務來等待一個HTTP請求,並且告訴它如何對其進行響應。註意
到,當我們調用$httpBackend.flush方法之前,響應是不會被發出的。
現在,
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'}]);
});
 在瀏覽器里,我們調用$httpBackend.flush()來清空(flush)請求隊列。這樣會使得$http服務返回的promise(什
麽是promise請參見這里)能夠被解釋成規範的應答。
AngularJS 入門教程

 我們設置一些斷言,來驗證手機數據模型已經在作用域里了。
最終,我們驗證orderProp的默認值被正確設置:
it('should set the default value of orderProp model', function() {
            expect(scope.orderProp).toBe('age');
});
;
執行./scripts/test.sh腳本來運行測試,你應該會看到如下輸出:
Chrome: Runner reset.
..
Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (3.00 ms)
Chrome 19.0.1084.36 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms)
練習
 在index.html末尾添加一個{{phones | json}}綁定,觀察json格式的手機列表。
 在PhoneListCtrl控制器中,把HTTP應答預處理一下,使得只顯示手機列表的前五個。在$http回調函數里面使用如
下代碼:
 $scope.phones = data.splice(0, 5);
總結
現在你應該感覺得到使用AngularJS的服務是多麽的容易(這都要歸功於AngularJS服務的依賴註入機制),轉到步驟
6,你會為手機添加縮略圖和鏈接。


AngularJS入門教程06:鏈接與圖片模板
這一步,你會為手機列表的手機添加縮略圖以及一些鏈接,不過這些鏈接還不會起作用。接下來你會使用這些鏈接來
分類顯示手機的額外信息。
請重置工作目錄:
git checkout -f step-6
現在你應該能夠看到列表里面手機的圖片和鏈接了。
步驟5和步驟6之間最重要的不同在下面列出。你可以在GitHub里看到完整的差別。
數據
註意到現在phones.json文件包含了唯一標識符和每一部手機的圖像鏈接。這些url現在指向app/img/phones/目錄。
app/phones/phones.json(樣例片段)
[
    {
        ...
        "id": "motorola-defy-with-motoblur",
        "imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg",
        "name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
        ...
    },
    ...
]
模板
app/index.html
...
<ul class="phones">
    <li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
    <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
    <a href="#/phones/{{phone.id}}">{{phone.name}}</a>
    <p>{{phone.snippet}}</p>
    </li>
</ul>
...
這些鏈接將來會指向每一部電話的詳細信息頁。不過現在為了產生這些鏈接,我們在href屬性里面使用我們早已熟悉
的雙括號數據綁定。在步驟2,我們添加了{{phone.name}}綁定作為元素內容。在這一步,我們在元素屬性中使用{{phone.i
d}}綁定。
我們同樣為每條記錄添加手機圖片,只需要使用ngSrc指令代替<img>的src屬性標簽就可以了。如果我們僅僅用一個正
常src屬性來進行綁定(<img class="diagram" src="{{phone.imageUrl}}">),瀏覽器會把AngularJS的{{ 表達式 }}標記直接進行字面
解釋,並且發起一個向非法urlhttp://localhost:8000/app/{{phone.imageUrl}}的請求。因為瀏覽器載入頁面時,同時也會請求載
入圖片,AngularJS在頁面載入完畢時才開始編譯——瀏覽器請求載入圖片時{{phone.imageUrl}}還沒得到編譯!有了這個n
gSrc指令會避免產生這種情況,使用ngSrc指令防止瀏覽器產生一個指向非法地址的請求。
測試
test/e2e/scenarios.js
AngularJS 入門教程

...
it('should render phone specific links', function() {
    input('query').enter('nexus');
    element('.phones li a').click();
    expect(browser().location().url()).toBe('/phones/nexus-s');
});
...
我們添加了一個新的端到端測試來驗證應用為手機視圖產生了正確的鏈接,上面就是我們的實現。
你現在可以刷新你的瀏覽器,並且用端到端測試器來觀察測試的運行,或者你可以在AngularJS服務器上運行它們。
練習
將ng-src指令換成普通的src屬性。用像Firebug,Chrome Web Inspector這樣的工具,或者直接去看服務器的訪問日誌,
你會發現你的應用向/app/%7B%7Bphone.imageUrl%7D%7D(或者/app/{{phone.imageUrl}})發送了一個非法請求。
這個問題是由於瀏覽器會在遇到img標簽的時候立刻向還未得到編譯的URL地址發送一個請求,AngularJS只有在頁面載
入完畢後才開始編譯表達式從而得到正確的圖片URL地址。
總結
如今你已經添加了手機圖片和鏈接,轉到步驟7,我們將學習AngularJS的布局模板以及AngularJS是如何輕易地為應
用提供多重視圖。


AngularJS入門教程07:路由與多視圖
在這一步,你將學習如何創建一個布局模板並且通過路由功能來構建一個具有多個視圖的應用。
請重置工作目錄:
git checkout -f step-7
註意到現在當你轉到app/index.html時,你會被重定向到app/index.html#/phones並且相同的手機列表在瀏覽器中顯示了出來。
當你點擊一個手機鏈接時,一個手機詳細信息列表也被顯示了出來。
步驟6和步驟7之間最重要的不同在下面列出。你可以在GitHub里看到完整的差別。
多視圖,路由和布局模板
我們的應用正慢慢發展起來並且變得逐漸複雜。在步驟7之前,應用只給我們的用戶提供了一個簡單的界面(一張所
有手機的列表),並且所有的模板代碼位於index.html文件中。下一步是增加一個能夠顯示我們列表中每一部手機詳細
信息的頁面。
為了增加詳細信息視圖,我們可以拓展index.html來同時包含兩個視圖的模板代碼,但是這樣會很快給我們帶來巨大的
麻煩。相反,我們要把index.html模板轉變成“布局模板”。這是我們應用所有視圖的通用模板。其他的“局部布局模
板”隨後根據當前的“路由”被充填入,從而形成一個完整視圖展示給用戶。
AngularJS中應用的路由通過$routeProvider來聲明,它是$route服務的提供者。這項服務使得控制器、視圖模板與
當前瀏覽器的URL可以輕易集成。應用這個特性我們就可以實現深鏈接,它允許我們使用瀏覽器的歷史(回退或者前進
導航)和書簽。
關於依賴註入(DI),註入器(Injector)和服務提供者(Providers)
正如從前面你學到的,依賴註入是AngularJS的核心特性,所以你必須要知道一點這家夥是怎麽工作的。
當應用引導時,AngularJS會創建一個註入器,我們應用後面所有依賴註入的服務都會需要它。這個註入器自己並不
知道$http和$route是幹什麽的,實際上除非它在模塊定義的時候被配置過,否則它根本都不知道這些服務的存在。註入
器唯一的職責是載入指定的服務模塊,在這些模塊中註冊所有定義的服務提供者,並且當需要時給一個指定的函數註
入依賴(服務)。這些依賴通過它們的提供者“懶惰式”(需要時才加載)實例化。
提供者是提供(創建)服務實例並且對外提供API接口的對象,它可以被用來控制一個服務的創建和運行時行為。對
於$route服務來說,$routeProvider對外提供了API接口,通過API接口允許你為你的應用定義路由規則。
AngularJS模塊解決了從應用中刪除全局狀態和提供方法來配置註入器這兩個問題。和AMD或者require.js這兩個模塊(非
AngularJS的兩個庫)不同的是,AngularJS模塊並沒有試圖去解決腳本加載順序以及懶惰式腳本加載這樣的問題。這
些目標和AngularJS要解決的問題毫無關聯,所以這些模塊完全可以共存來實現各自的目標。
App 模塊
app/js/app.js
angular.module('phonecat', []).
config(['$routeProvider', function($routeProvider) {
    $routeProvider.
    when('/phones', {templateUrl: 'partials/phone-list.html', controller: PhoneListCtrl}).
    AngularJS 入門教程

    when('/phones/:phoneId', {templateUrl: 'partials/phone-detail.html', controller: PhoneDetailCtrl}).
    otherwise({redirectTo: '/phones'});
}]);
為了給我們的應用配置路由,我們需要給應用創建一個模塊。我們管這個模塊叫做phonecat,並且通過使用configAPI,
我們請求把$routeProvider註入到我們的配置函數並且使用$routeProvider.whenAPI來定義我們的路由規則。
註意到在註入器配置階段,提供者也可以同時被註入,但是一旦註入器被創建並且開始創建服務實例的時候,他們就
不再會被外界所獲取到。
我們的路由規則定義如下
 當URL 映射段為/phones 時,手機列表視圖會被顯示出來。為了構造這個視圖,AngularJS 會使用
phone-list.html 模板和PhoneListCtrl 控制器。
 當URL 映射段為/phone/:phoneId 時,手機詳細信息視圖被顯示出來。這里:phoneId 是URL 的變量部分。
為了構造手機詳細視圖,AngularJS 會使用phone-detail.html 模板和PhoneDetailCtrl 控制器。
我們重用之前創造過的PhoneListCtrl控制器,同時我們為手機詳細視圖添加一個新的PhoneDetailCtrl控制器,把它存放在app
/js/controllers.js文件里。
$route.otherwise({redirectTo: '/phones'})語句使得當瀏覽器地址不能匹配我們任何一個路由規則時,觸發重定向到/phones。
註意到在第二條路由聲明中:phoneId參數的使用。$route服務使用路由聲明/phones/:phoneId作為一個匹配當前URL的模板。所
有以:符號聲明的變量(此處變量為phones)都會被提取,然後存放在$routeParams對象中。
為了讓我們的應用引導我們新創建的模塊,我們同時需要在ngApp指令的值上指明模塊的名字:
app/index.html
<!doctype html>
<html lang="en" ng-app="phonecat">
...
控制器
app/js/controllers.js
...
function PhoneDetailCtrl($scope, $routeParams) {
    $scope.phoneId = $routeParams.phoneId;
}
//PhoneDetailCtrl.$inject = ['$scope', '$routeParams'];
模板
$route服務通常和ngView指令一起使用。ngView指令的角色是為當前路由把對應的視圖模板載入到布局模板中。
app/index.html
<html lang="en" ng-app="phonecat">
<head>
...
<script src="lib/angular/angular.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
</head>
<body>


<div ng-view></div>
</body>
</html>
註意,我們把index.html模板里面大部分代碼移除,我們只放置了一個<div>容器,這個<div>具有ng-view屬性。我們刪除掉
的代碼現在被放置在phone-list.html模板中:
app/partials/phone-list.html
<div class="container-fluid">
    <div class="row-fluid">
        <div class="span2">
        <!--Sidebar content-->
        Search: <input ng-model="query">
        Sort by:
        <select ng-model="orderProp">
        <option value="name">Alphabetical</option>
        <option value="age">Newest</option>
        </select>
        </div>
        <div class="span10">
        <!--Body content-->
        <ul class="phones">
            <li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
            <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
            <a href="#/phones/{{phone.id}}">{{phone.name}}</a>
            <p>{{phone.snippet}}</p>
            </li>
        </ul>
        </div>
    </div>
</div>
同時我們為手機詳細信息視圖添加一個占位模板。
app/partials/phone-detail.html
TBD: detail view for {{phoneId}}
註意到我們的布局模板中沒再添加PhoneListCtrl或PhoneDetailCtrl控制器屬性!
測試
為了自動驗證所有的東西都良好地集成起來,我們需要寫一些端到端測試,導航到不同的URL上然後驗證正確地視圖
被渲染出來。
...
it('should redirect index.html to index.html#/phones', function() {
    browser().navigateTo('../../app/index.html');
    expect(browser().location().url()).toBe('/phones');
});
...
describe('Phone detail view', function() {
    beforeEach(function() {
        browser().navigateTo('../../app/index.html#/phones/nexus-s');
    });
    it('should display placeholder page with phoneId', function() {
        expect(binding('phoneId')).toBe('nexus-s');
    });
    AngularJS 入門教程

});
你現在可以刷新你的瀏覽器,然後重新跑一遍端到端測試,或者你可以在AngularJS的服務器上運行一下。
練習
試著在index.html上增加一個{{orderProp}}綁定,當你在手機列表視圖上時什麽也沒變。這是因為orderProp模型僅僅在PhoneLis
tCtrl管理的作用域下才是可見的,這與<div ng-view>元素相關。如果你在phone-list.html模板中加入同樣的綁定,那麽這個
綁定會按你設想的那樣被渲染出來。
總結
設置路由並實現手機列表視圖之後,我們已經可以進入步驟8來實現手機詳細信息視圖了。


AngularJS入門教程08:更多模板
在這一步,你將實現手機詳細信息視圖,這個視圖會在用戶點擊手機列表中的一部手機時被顯示出來。
請重置工作目錄:
git checkout -f step-8
現在當你點擊列表中的一部手機之後,這部手機的詳細信息頁面就會被顯示出來。
為了實現手機詳細信息視圖我們將會使用$http來獲取數據,同時我們也要增添一個phone-detail.html視圖模板。
步驟7和步驟8之間最重要的不同在下面列出。你可以在GitHub里看到完整的差別。
數據
除了phones.json,app/phones/目錄也包含了每一部手機信息的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"
    }
}
這些文件中的每一個都用相同的數據結構描述了一部手機的不同屬性。我們會在手機詳細信息視圖中顯示這些數據。
控制器
我們使用$http服務獲取數據,以此來拓展我們的PhoneListCtrl。這和之前的手機列表控制器的工作方式是一樣的。
app/js/controllers.js
function PhoneDetailCtrl($scope, $routeParams, $http) {
    $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
        $scope.phone = data;
    });
}
//PhoneDetailCtrl.$inject = ['$scope', '$routeParams', '$http'];
為了構造HTTP請求的URL,我們需要$route服務提供的當前路由中抽取$routeParams.phoneId。
模板
AngularJS 入門教程

phone-detail.html文件中原有的TBD占位行已經被列表和構成手機詳細信息的綁定替換掉了。註意到,這里我們使用
AngularJS的{{表達式}}標記和ngRepeat來在視圖中渲染數據模型。
app/partials/phone-detail.html
<img ng-src="{{phone.images[0]}}" class="phone">
<h1>{{phone.name}}</h1>
<p>{{phone.description}}</p>
    <ul class="phone-thumbs">
        <li ng-repeat="img in 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 phone.availability">{{availability}}</dd>
        </dl>
    </li>
...
</li>
<span>Additional Features</span>
<dd>{{phone.additionalFeatures}}</dd>
</li>
</ul>
測試
我們來寫一個新的單元測試,這個測試和我們在步驟5中為PhoneListCtrl寫的那個很像。
test/unit/controllersSpec.js
...
describe('PhoneDetailCtrl', function(){
    var scope, $httpBackend, ctrl;
    beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {
        $httpBackend = _$httpBackend_;
        $httpBackend.expectGET('phones/xyz.json').respond({name:'phone xyz'});
        $routeParams.phoneId = 'xyz';
        scope = $rootScope.$new();
        ctrl = $controller(PhoneDetailCtrl, {$scope: scope});
    }));
    it('should fetch phone detail', function() {
        expect(scope.phone).toBeUndefined();
        $httpBackend.flush();
        expect(scope.phone).toEqual({name:'phone xyz'});
    });
});
...
執行./scripts/test.sh腳本來執行測試,你應該會看到如下輸出:
Chrome: Runner reset.
...
Total 3 tests (Passed: 3; Fails: 0; Errors: 0) (5.00 ms)
Chrome 19.0.1084.36 Mac OS: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (5.00 ms)
同時,我們也添加一個端到端測試,指向Nexus S手機詳細信息頁面並且驗證頁面的頭部是“Nexus S”。


test/e2e/scenarios.js
...
describe('Phone detail view', function() {
beforeEach(function() {
            browser().navigateTo('../../app/index.html#/phones/nexus-s');
});
it('should display nexus-s page', function() {
        expect(binding('phone.name')).toBe('Nexus S');
});
});
...
你現在可以刷新你的瀏覽器,然後重新跑一遍端到端測試,或者你可以在AngularJS的服務器上運行一下。
練習
使用AngularJS端到端測試API寫一個測試,用它來驗證我們在Nexus S詳細信息頁面顯示的四個縮略圖。
總結
現在手機詳細頁面已經就緒了,在步驟9中我們將學習如何寫一個顯示過濾器。
AngularJS 入門教程

AngularJS入門教程09:過濾器
在這一步你將學習到如何創建自己的顯示過濾器。
請重置工作目錄:
git checkout -f step-9
現在轉到一個手機詳細信息頁面。在上一步,手機詳細頁面顯示“true”或者“false”來說明某個手機是否具有特
定的特性。現在我們使用一個定制的過濾器來把那些文本串圖形化:√作為“true”;以及×作為“false”。來讓
我們看看過濾器代碼長什麽樣子。
步驟8和步驟9之間最重要的不同在下面列出。你可以在GitHub里看到完整的差別。
定制過濾器
為了創建一個新的過濾器,先創建一個phonecatFilters模塊,並且將定制的過濾器註冊給這個模塊。
app/js/filters.js
angular.module('phonecatFilters', []).filter('checkmark', function() {
    return function(input) {
         return input ? '\u2713' : '\u2718';
    };
});
我們的過濾器命名為checkmark。它的輸入要麽是true,要麽是false,並且我們返回兩個表示true或false的unicode字
符(\u2713和\u2718)。
現在我們的過濾器準備好了,我們需要將我們的phonecatFilters模塊作為一個依賴註冊到我們的主模塊phonecat上。
app/js/app/js
...
angular.module('phonecat', ['phonecatFilters']).
...
模板
由於我們的模板代碼寫在app/js/filter.js文件中,所以我們需要在布局模板中引入這個文件。
app/index.html
...
<script src="js/controllers.js"></script>
<script src="js/filters.js"></script>
...
在AngularJS模板中使用過濾器的語法是:
{{ expression | filter }}
我們把過濾器應用到手機詳細信息模板中:
app/partials/phone-detail.html


...
<dl>
<dt>Infrared</dt>
<dd>{{phone.connectivity.infrared | checkmark}}</dd>
<dt>GPS</dt>
<dd>{{phone.connectivity.gps | checkmark}}</dd>
</dl>
...
測試
過濾器和其他組件一樣,應該被測試,並且這些測試實際上很容易完成。
test/unit/filtersSpec.js
describe('filter', function() {
    beforeEach(module('phonecatFilters'));
    describe('checkmark', function() {
         it('should convert boolean values to unicode checkmark or cross',
    inject(function(checkmarkFilter) {
        expect(checkmarkFilter(true)).toBe('\u2713');
        expect(checkmarkFilter(false)).toBe('\u2718');
    }));
    });
});
註意在執行任何過濾器測試之前,你需要為phonecatFilters模塊配置我們的測試註入器。
執行./scripts/test/sh運行測試,你應該會看到如下的輸出:
Chrome: Runner reset.
....
Total 4 tests (Passed: 4; Fails: 0; Errors: 0) (3.00 ms)
Chrome 19.0.1084.36 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms)
練習
 現在讓我們來練習一下AngularJS內置過濾器,在index.html中加入如下綁定:
o {{ "lower cap string" | uppercase }}
o {{ {foo: "bar", baz: 23} | json }}
o {{ 1304375948024 | date }}
o {{ 1304375948024 | date:"MM/dd/yyyy @ h:mma" }}
 我們也可以用一個輸入框來創建一個模型,並且將之與一個過濾後的綁定結合在一起。在index.html中加入
如下代碼:
 <input ng-model="userInput"> Uppercased: {{ userInput | uppercase }}
總結
現在你已經知道了如何編寫和測試一個定制化插件,在步驟10中我們會學習如何用AngularJS繼續豐富我們的手機詳
細信息頁面。
AngularJS 入門教程

AngularJS入門教程10:事件處理器
在這一步,你會在手機詳細信息頁面讓手機圖片可以點擊。
請重置工作目錄:
git checkout -f step-10
手機詳細信息視圖展示了一幅當前手機的大號圖片,以及幾個小一點的縮略圖。如果用戶點擊縮略圖就能把那張大的
替換成自己那就更好了。現在我們來看看如何用AngularJS來實現它。
步驟9和步驟10之間最重要的不同在下面列出。你可以在GitHub里看到完整的差別。
控制器
app/js/controllers.js
...
function PhoneDetailCtrl($scope, $routeParams, $http) {
    $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
        $scope.phone = data;
        $scope.mainImageUrl = data.images[0];
    });
    $scope.setImage = function(imageUrl) {
         $scope.mainImageUrl = imageUrl;
    }
}
//PhoneDetailCtrl.$inject = ['$scope', '$routeParams', '$http'];
在PhoneDetailCtrl控制器中,我們創建了mainImageUrl模型屬性,並且把它的默認值設為第一個手機圖片的URL。
模板
app/partials/phone-detail.html
<img ng-src="{{mainImageUrl}}" class="phone">
...
<ul class="phone-thumbs">
    <li ng-repeat="img in phone.images">
    <img ng-src="{{img}}" ng-click="setImage(img)">
    </li>
</ul>
...
我們把大圖片的ngSrc指令綁定到mainImageUrl屬性上。
同時我們註冊一個ngClick處理器到縮略圖上。當一個用戶點擊縮略圖的任意一個時,這個處理器會使用setImage事件
處理函數來把mainImageUrl屬性設置成選定縮略圖的URL。
測試
為了驗證這個新特性,我們添加了兩個端到端測試。一個驗證主圖片被默認設置成第一個手機圖片。第二個測試點擊
幾個縮略圖並且驗證主圖片隨之合理的變化。


test/e2e/scenarios.js
...
describe('Phone detail view', function() {
...
it('should display the first phone image as the main phone image', function() {
    expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
});
it('should swap main image if a thumbnail image is clicked on', function() {
    element('.phone-thumbs li:nth-child(3) img').click();
    expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.2.jpg');
    element('.phone-thumbs li:nth-child(1) img').click();
    expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
});
});
});
你現在可以刷新你的瀏覽器,然後重新跑一遍端到端測試,或者你可以在AngularJS的服務器上運行一下。
練習
為PhoneDetailCtrl添加一個新的控制器方法:
$scope.hello = function(name) {
    alert('Hello ' + (name || 'world') + '!');
}
並且添加:
<button ng-click="hello('Elmo')">Hello</button>
到phone-details.html模板。
總結
現在圖片瀏覽器已經做好了,我們已經為步驟11(最後一步啦!)做好了準備,我們會學習用一種更加優雅的方式來
獲取數據。
AngularJS 入門教程

AngularJS入門教程11:REST和定制服務
在這一步中,我們會改進我們APP獲取數據的方式。
請重置工作目錄:
git checkout -f step-11
對我們應用所做的最後一個改進就是定義一個代表RESTful客戶端的定制服務。有了這個客戶端我們可以用一種更簡
單的方式來發送XHR請求,而不用去關心更底層的$http服務(API、HTTP方法和URL)。
步驟9和步驟10之間最重要的不同在下面列出。你可以在GitHub里看到完整的差別。
模板
定制的服務被定義在app/js/services,所以我們需要在布局模板中引入這個文件。另外,我們也要加載angularjs-r
esource.js這個文件,它包含了ngResource模塊以及其中的$resource服務,我們一會就會用到它們:
app/index.html
...
<script src="js/services.js"></script>
<script src="lib/angular/angular-resource.js"></script>
...
服務
app/js/services.js
angular.module('phonecatServices', ['ngResource']).
factory('Phone', function($resource){
        return $resource('phones/:phoneId.json', {}, {
            query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
        });
});
我們使用模塊API通過一個工廠方法註冊了一個定制服務。我們傳入服務的名字Phone和工廠函數。工廠函數和控制器構
造函數差不多,它們都通過函數參數聲明依賴服務。Phone服務聲明了它依賴於$resource服務。
$resource服務使得用短短的幾行代碼就可以創建一個RESTful客戶端。我們的應用使用這個客戶端來代替底層的$htt
p服務。
app/js/app.js
...
angular.module('phonecat', ['phonecatFilters', 'phonecatServices']).
...
我們需要把phonecatServices添加到phonecat的依賴數組里。
控制器
通過重構掉底層的$http服務,把它放在一個新的服務Phone中,我們可以大大簡化子控制器(PhoneListCtrl和PhoneDetailCtr
l)。AngularJS的$resource相比於$http更加適合於與RESTful數據源交互。而且現在我們更容易理解控制器這些代碼
在幹什麽了。


app/js/controllers.js
...
function PhoneListCtrl($scope, Phone) {
    $scope.phones = Phone.query();
    $scope.orderProp = 'age';
}
//PhoneListCtrl.$inject = ['$scope', 'Phone'];
function PhoneDetailCtrl($scope, $routeParams, Phone) {
    $scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
        $scope.mainImageUrl = phone.images[0];
    });
    $scope.setImage = function(imageUrl) {
        $scope.mainImageUrl = imageUrl;
    }
}
//PhoneDetailCtrl.$inject = ['$scope', '$routeParams', 'Phone'];
註意到,在PhoneListCtrl里我們把:
$http.get('phones/phones.json').success(function(data) {
    $scope.phones = data;
});
換成了:
$scope.phones = Phone.query();
我們通過這條簡單的語句來查詢所有的手機。
另一個非常需要註意的是,在上面的代碼里面,當調用Phone服務的方法是我們並沒有傳遞任何回調函數。盡管這看
起來結果是同步返回的,其實根本就不是。被同步返回的是一個“future”——一個對象,當XHR相應返回的時候會
填充進數據。鑒於AngularJS的數據綁定,我們可以使用future並且把它綁定到我們的模板上。然後,當數據到達時,
我們的視圖會自動更新。
有的時候,單單依賴future對象和數據綁定不足以滿足我們的需求,所以在這些情況下,我們需要添加一個回調函數
來處理服務器的響應。PhoneDetailCtrl控制器通過在一個回調函數中設置mainImageUrl就是一個解釋。
測試
修改我們的單元測試來驗證我們新的服務會發起HTTP請求並且按照預期地處理它們。測試同時也檢查了我們的控制器
是否與服務正確協作。
$resource服務通過添加更新和刪除資源的方法來增強響應得到的對象。如果我們打算使用toEqual匹配器,我們的測試
會失敗,因為測試值並不會和響應完全等同。為了解決這個問題,我們需要使用一個最近定義的toEqualDataJasmine匹
配器。當toEqualData匹配器比較兩個對象的時候,它只考慮對象的屬性而忽略掉所有的方法。
test/unit/controllersSpec.js:
describe('PhoneCat controllers', function() {
beforeEach(function(){
    this.addMatchers({
    toEqualData: function(expected) {
        return angular.equals(this.actual, expected);
    }
    AngularJS 入門教程

    });
});
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).toEqual([]);
    $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());
    });
});
});
執行./scripts/test.sh運行測試,你應該會看到如下的輸出:
Chrome: Runner reset.
....
Total 4 tests (Passed: 4; Fails: 0; Errors: 0) (3.00 ms)
Chrome 19.0.1084.36 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms)
總結
完工!你在相當短的時間內已經創建了一個Web應用。在完結篇里面我們會提起接下來應該幹什麽。


AngularJS入門教程:完結篇
我們的應用現在完成了。你可以隨意練習這些代碼,用git checkout或者goto_step.sh命令切換到之前的步驟。
對於更多我們在教程部分提及的細節以及AngularJS理論的例子,你可以在開發指南中找到。
一些更多的例子,請參照Cookbook。
當你準備好使用AngularJS創建一個新項目時,我們推薦使用AngularJS種子項目來引導你的開發。
我們希望這篇教程對你有用,讓你對AngularJS有了足夠的了解,並且願意對其進行更加深入的學習。我們特別期待
著你能夠開發出自己的AngularJS應用,或者讓你對AngularJS貢獻代碼產生興趣。
如果你有什麽問題,反饋,或是想跟我們打個招呼,可在社區論壇交流,或者直接向https://groups.google.com/fo
rum/#!forum/angular發消息吧!

留言

這個網誌中的熱門文章

c語言-關於#define用法

CMD常用網管指令

PHP 與 JavaScript 之間傳值利用 json