本章我們將一起學(xué)習(xí):
- 如何創(chuàng)建一個(gè)布局模版
- 如何如何創(chuàng)建一個(gè)多視圖路由應(yīng)用
- 如何使用
ngRoute
指令實(shí)現(xiàn)視圖路由
我們先看看本章對(duì)應(yīng)的例子要做的事情是什么:
- 當(dāng)你點(diǎn)擊導(dǎo)航欄導(dǎo)航到
app/index.html
時(shí)埋哟,你將被重定向到app/index.html/#/phones
痰憎,它會(huì)顯示手機(jī)列表芜抒。 - 當(dāng)你點(diǎn)擊一個(gè)手機(jī)鏈接時(shí),URL地址欄的地址變化(顯然)毅人,新的頁面將顯示手機(jī)的一些信息吭狡。
【注解】本章需要一定Provider提供者的知識(shí),可以先看本章教程丈莺,如果看完之后想系統(tǒng)學(xué)習(xí)下提供者划煮、服務(wù)相關(guān)的知識(shí),點(diǎn)擊AngularJS服務(wù)體系查看缔俄。
下面我們把代碼切換到step-7:
git checkout -f step-7
假設(shè)你已經(jīng)運(yùn)行了網(wǎng)站般此,你只需要刷新你的瀏覽器來看效果∏O郑或者你可以在線看效果。
相關(guān)依賴
我們通過ngRoute
指令實(shí)現(xiàn)視圖路由技術(shù)邀桑,它不屬于Angular核心框架瞎疼。
我們將使用Bower
來安裝客戶端依賴。以下是新的bower.json
內(nèi)容:
{
"name": "angular-phonecat",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-phonecat",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.4.x",
"angular-mocks": "1.4.x",
"jquery": "~2.2.1",
"bootstrap": "~3.1.1",
"angular-route": "1.4.x"
}
}
"angular-route": "1.4.x"
這句話告訴bower安裝1.4.x版本的Angular路由插件壁畸。我們需要保證路由插件的成功安裝贼急。
如果你已經(jīng)全局安裝了bower管理器,你可以通過bower install
來安裝所有的依賴捏萍。但是對(duì)于本項(xiàng)目來說太抓,我們已經(jīng)在npm腳本中配置了bower install
的執(zhí)行,所以我們只需要運(yùn)行:
npm install
注意:如果你運(yùn)行上述命令后令杈,恰好一個(gè)新的Angular版本發(fā)布了走敌,這時(shí)候,
bower install
命令可能會(huì)執(zhí)行失敗逗噩,因?yàn)樾屡f版本的AngularJS有沖突掉丽。如果你遇到了這個(gè)問題,你只需要?jiǎng)h除你的app/bower_components
文件夾异雁,再次運(yùn)行npm install
捶障。
多視圖,路由技術(shù)和布局模版
我們的應(yīng)用內(nèi)容越來越豐富了纲刀。在本章之前项炼,應(yīng)用只有一個(gè)視圖(手機(jī)列表),并且所有的模版代碼都在index.html
文件中。下面我們將為列表中的每一個(gè)手機(jī)添加一個(gè)詳情視圖锭部。
我們可以直接把詳情視圖添加到inde.html
中暂论,但是這樣做會(huì)讓index.html
變得特別大,看起來會(huì)很糟糕空免。所以我們換一種做法空另,我們將把index.html
分解成布局模版
。這個(gè)模版適用于本應(yīng)用中的所有頁面(注解:這和sitemesh蹋砚、velocity扼菠、apache Tiles等服務(wù)端模版技術(shù)對(duì)應(yīng))。其他的局部模版
將根據(jù)當(dāng)前的路由被選擇性的集成到當(dāng)前模版中坝咐,從而最終呈現(xiàn)給用戶循榆。
Angular使用$routeProvider
來申明應(yīng)用應(yīng)用路由,它是$route service
的提供者墨坚。這個(gè)服務(wù)可以很容易的把控制器秧饮、視圖模版和當(dāng)前的URL地址綁定在一起。通過這個(gè)特性泽篮,我們可以實(shí)現(xiàn)deep linking盗尸,通過它我們可以利用瀏覽器的歷史功能(往后和前進(jìn))以及書簽功能。
探討下依賴注入帽撑、注入器和提供者
依賴注入功能是AngularJS的核心功能泼各,所以了解一點(diǎn)它的知識(shí)是有必要的。
當(dāng)應(yīng)用啟動(dòng)時(shí)亏拉,AngularJS將先創(chuàng)建一個(gè)注入器扣蜻,它將尋找并且注入應(yīng)用所需要的所有服務(wù)。注入器本身是不知道$http
或者$route
服務(wù)是干嘛的及塘。實(shí)時(shí)上莽使,注入器都不知道它們的存在,除非他們被依賴進(jìn)來笙僚。
注入器只完成以下幾個(gè)步驟:
- 加載應(yīng)用中指定的模塊定義
- 注冊(cè)模塊定義中所有的提供器(提供者)——配置階段
- 當(dāng)需要的時(shí)候芳肌,注入服務(wù)或者提供者延遲初始化的依賴等±卟悖——運(yùn)行階段
提供者的功能為創(chuàng)建服務(wù)實(shí)例和暴露配置API庇勃,后者包括控制服務(wù)的創(chuàng)建和運(yùn)行時(shí)行為。在$route
服務(wù)中槽驶,$routeProvider
暴露一些允許你定義路由的API责嚷。
注意:提供者只能在
config
函數(shù)里被注入。所以你無法把$routeProvider
注入到PhoneListCtrl
中掂铐。
Angular模塊解決了移除應(yīng)用全局狀態(tài)的問題罕拂,并且提供了一個(gè)配置注入器的方式揍异。跟AMD
或者require.js
不一樣的是,Angular模塊不會(huì)去解決腳本加載順序或者延遲腳本獲取的問題爆班。這些目標(biāo)是相互獨(dú)立的衷掷,他們可以共同工作于一個(gè)系統(tǒng)中,各自完成自己的目標(biāo)(意思就是AngularJS和AMD
或者require.js
框架可以公用)柿菩。
如果你想進(jìn)一步了解Angular中的依賴注入戚嗅,可以看看這里:理解依賴注入.
模版
$route
指令通常和$ngView
指令結(jié)合使用。$ngView
指令所扮演的角色是把當(dāng)前路由對(duì)應(yīng)的視圖模版加入布局模版中枢舶。所以這里使用它正好懦胞。
注意:從AngularJS 1.2起,
ngRoute
就是Angular的自有模塊了凉泄,而且必須通過加載另外的angular-route.js
文件來完成載入(由Bower下載)
app/index.html
<!doctype html>
<html lang="en" ng-app="phonecatApp">
<head>
...
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
</head>
<body>
<div ng-view></div>
</body>
</html>
這段代碼中躏尉,我們新增了兩個(gè)<script>標(biāo)簽,它們是用來加載額外的Javascript的:
-
angular-route.js
: 定義了AngularngRoute
模塊后众,將提供我們路由操作胀糜。 -
app.js
: 這個(gè)文件包含了我們的根模塊。
可以注意到這個(gè)index.html
模版中的大部分代碼已經(jīng)被移出了蒂誉,取而代之的是一個(gè)含有ng-view
屬性的<div>
標(biāo)簽教藻。被移出去的部分我們重新放在了phone-list.html
模版。
app/partials/phone-list.html
:
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<!--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="col-md-10">
<!--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>
同樣的右锨,我們也為手機(jī)詳情視圖創(chuàng)建一個(gè)占位模版:
app/partials/phone-detail.html
:
TBD: detail view for <span>{{phoneId}}</span>
注意這里我們是如何使用phoneId
(將在PhoneDetailCtrl
控制器中被定義)表達(dá)式的怖竭。
本章我們將一起學(xué)習(xí):
- 如何創(chuàng)建一個(gè)布局模版
- 如何如何創(chuàng)建一個(gè)多視圖路由應(yīng)用
- 如何使用
ngRoute
指令實(shí)現(xiàn)視圖路由
我們先看看本章對(duì)應(yīng)的例子要做的事情是什么:
- 當(dāng)你點(diǎn)擊導(dǎo)航欄導(dǎo)航到
app/index.html
時(shí),你將被重定向到app/index.html/#/phones
陡蝇,它會(huì)顯示手機(jī)列表。 - 當(dāng)你點(diǎn)擊一個(gè)手機(jī)鏈接時(shí)哮肚,URL地址欄的地址變化(顯然)登夫,新的頁面將顯示手機(jī)的一些信息。
下面我們把代碼切換到step-7:
git checkout -f step-7
假設(shè)你已經(jīng)運(yùn)行了網(wǎng)站允趟,你只需要刷新你的瀏覽器來看效果恼策。或者你可以在線看效果潮剪。
相關(guān)依賴
我們通過ngRoute
指令實(shí)現(xiàn)視圖路由技術(shù)涣楷,它不屬于Angular核心框架。
我們將使用Bower
來安裝客戶端依賴抗碰。以下是新的bower.json
內(nèi)容:
{
"name": "angular-phonecat",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-phonecat",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.4.x",
"angular-mocks": "1.4.x",
"jquery": "~2.2.1",
"bootstrap": "~3.1.1",
"angular-route": "1.4.x"
}
}
"angular-route": "1.4.x"
這句話告訴bower安裝1.4.x版本的Angular路由插件狮斗。我們需要保證路由插件的成功安裝。
如果你已經(jīng)全局安裝了bower管理器弧蝇,你可以通過bower install
來安裝所有的依賴碳褒。但是對(duì)于本項(xiàng)目來說折砸,我們已經(jīng)在npm腳本中配置了bower install
的執(zhí)行,所以我們只需要運(yùn)行:
npm install
注意:如果你運(yùn)行上述命令后沙峻,恰好一個(gè)新的Angular版本發(fā)布了睦授,這時(shí)候,
bower install
命令可能會(huì)執(zhí)行失敗摔寨,因?yàn)樾屡f版本的AngularJS有沖突去枷。如果你遇到了這個(gè)問題,你只需要?jiǎng)h除你的app/bower_components
文件夾删顶,再次運(yùn)行npm install
。
多視圖佑笋,路由技術(shù)和布局模版
我們的應(yīng)用內(nèi)容越來越豐富了翼闹。在本章之前,應(yīng)用只有一個(gè)視圖(手機(jī)列表)蒋纬,并且所有的模版代碼都在index.html
文件中猎荠。下面我們將為列表中的每一個(gè)手機(jī)添加一個(gè)詳情視圖。
我們可以直接把詳情視圖添加到inde.html
中蜀备,但是這樣做會(huì)讓index.html
變得特別大关摇,看起來會(huì)很糟糕。所以我們換一種做法碾阁,我們將把index.html
分解成布局模版
输虱。這個(gè)模版適用于本應(yīng)用中的所有頁面(注解:這和sitemesh、velocity脂凶、apache Tiles等服務(wù)端模版技術(shù)對(duì)應(yīng))宪睹。其他的局部模版
將根據(jù)當(dāng)前的路由被選擇性的集成到當(dāng)前模版中,從而最終呈現(xiàn)給用戶蚕钦。
Angular使用$routeProvider
來申明應(yīng)用應(yīng)用路由亭病,它是$route service
的提供者。這個(gè)服務(wù)可以很容易的把控制器嘶居、視圖模版和當(dāng)前的URL地址綁定在一起罪帖。通過這個(gè)特性,我們可以實(shí)現(xiàn)deep linking邮屁,通過它我們可以利用瀏覽器的歷史功能(往后和前進(jìn))以及書簽功能整袁。
探討下依賴注入、注入器和提供者
依賴注入功能是AngularJS的核心功能佑吝,所以了解一點(diǎn)它的知識(shí)是有必要的坐昙。
當(dāng)應(yīng)用啟動(dòng)時(shí)腔长,AngularJS將先創(chuàng)建一個(gè)注入器畏鼓,它將尋找并且注入應(yīng)用所需要的所有服務(wù)。注入器本身是不知道$http
或者$route
服務(wù)是干嘛的。實(shí)時(shí)上哩盲,注入器都不知道它們的存在挨摸,除非他們被依賴進(jìn)來镇饮。
注入器只完成以下幾個(gè)步驟:
- 加載應(yīng)用中指定的模塊定義
- 注冊(cè)模塊定義中所有的提供器(提供者)——配置階段
- 當(dāng)需要的時(shí)候广鳍,注入服務(wù)或者提供者延遲初始化的依賴等〉埽——運(yùn)行階段
提供者的功能為創(chuàng)建服務(wù)實(shí)例和暴露配置API嗜历,后者包括控制服務(wù)的創(chuàng)建和運(yùn)行時(shí)行為。在$route
服務(wù)中抖所,$routeProvider
暴露一些允許你定義路由的API梨州。
注意:提供者只能在
config
函數(shù)里被注入。所以你無法把$routeProvider
注入到PhoneListCtrl
中田轧。
Angular模塊解決了移除應(yīng)用全局狀態(tài)的問題暴匠,并且提供了一個(gè)配置注入器的方式。跟AMD
或者require.js
不一樣的是傻粘,Angular模塊不會(huì)去解決腳本加載順序或者延遲腳本獲取的問題每窖。這些目標(biāo)是相互獨(dú)立的,他們可以共同工作于一個(gè)系統(tǒng)中弦悉,各自完成自己的目標(biāo)(意思就是AngularJS和AMD
或者require.js
框架可以公用)窒典。
如果你想進(jìn)一步了解Angular中的依賴注入,可以看看這里:理解依賴注入.
模版
$route
指令通常和$ngView
指令結(jié)合使用稽莉。$ngView
指令所扮演的角色是把當(dāng)前路由對(duì)應(yīng)的視圖模版加入布局模版中瀑志。所以這里使用它正好。
注意:從AngularJS 1.2起污秆,
ngRoute
就是Angular的自有模塊了劈猪,而且必須通過加載另外的angular-route.js
文件來完成載入(由Bower下載)
app/index.html
<!doctype html>
<html lang="en" ng-app="phonecatApp">
<head>
...
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
</head>
<body>
<div ng-view></div>
</body>
</html>
這段代碼中,我們新增了兩個(gè)<script>標(biāo)簽良拼,它們是用來加載額外的Javascript的:
-
angular-route.js
: 定義了AngularngRoute
模塊战得,將提供我們路由操作。 -
app.js
: 這個(gè)文件包含了我們的根模塊将饺。
可以注意到這個(gè)index.html
模版中的大部分代碼已經(jīng)被移出了,取而代之的是一個(gè)含有ng-view
屬性的<div>
標(biāo)簽痛黎。被移出去的部分我們重新放在了phone-list.html
模版予弧。
app/partials/phone-list.html
:
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<!--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="col-md-10">
<!--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>
同樣的,我們也為手機(jī)詳情視圖創(chuàng)建一個(gè)占位模版:
app/partials/phone-detail.html
:
TBD: detail view for <span>{{phoneId}}</span>
注意這里我們是如何使用phoneId
(將在PhoneDetailCtrl
控制器中被定義)表達(dá)式的湖饱。
應(yīng)用模塊
為了改善應(yīng)用的組織結(jié)構(gòu)掖蛤,我們使用了Angular的ngRoute
模塊,并且我們把控制器移動(dòng)到了它們自己的模塊phonecatControllers
(如下所示):
app/js/app.js
:
var phonecatApp = angular.module('phonecatApp', [
'ngRoute',
'phonecatControllers'
]);
...
我們把angular-route.js
文件引入了index.html
井厌,并且在controllers.js
中新建了一個(gè)phonecatControllers
模塊蚓庭。然而這些該不足以讓我們可以使用ngRoute
模塊和phonecatControllers
模塊致讥。我們還需要把他們作為應(yīng)用的依賴引入進(jìn)來。像上述代碼一樣器赞,我們把這兩個(gè)模塊引入本應(yīng)用垢袱,這樣我們就可以使用它們提供的指令和服務(wù)了。
注意上述代碼中第二個(gè)參數(shù)“['ngRoute', 'phonecatControllers']
”港柜,是一個(gè)字符串?dāng)?shù)組请契,代表了phonecatApp的依賴。
...
phonecatApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: 'PhoneListCtrl'
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: 'PhoneDetailCtrl'
}).
otherwise({
redirectTo: '/phones'
});
}]);
通過phonecatApp.config()
方法夏醉,我們把$routeProvider
注入了配置函數(shù)爽锥,并且允許我們使用$routeProvider.when()
方法來定義我們的路由。
我們的應(yīng)用路由定義如下:
-
when('/phones')
:當(dāng)URL哈希片段值為/phones
時(shí)畔柔,手機(jī)列表視圖將會(huì)被展示氯夷。Angular將使用phone-list.html
模版和PhoneListCtrl
控制器來完成視圖的展示。 -
when('/phones/:phoneId')
: 當(dāng)URL的哈希片段為/phones/:phoneId
時(shí)靶擦,手機(jī)詳情視圖將被展示腮考。其中,phoneId
是URL中變化的部分奢啥。Angular將使用phone-detail.html
模版和PhoneDetailCtrl
控制器完成視圖的展示秸仙。 -
otherwise({redirectTo: '/phones'})
: 其他情況下,將跳轉(zhuǎn)到手機(jī)列表頁桩盲。
為了展示手機(jī)詳情視圖的展示寂纪,我們重用了前面幾章中創(chuàng)建的PhoneListCtrl
控件,并且添加了一個(gè)新的PhoneDetailCtrl
控制器赌结。這些改變都在app/js/controllers.js
中捞蛋。
我們來看看第二個(gè)路由申明中的:phoneId
參數(shù)。$route
服務(wù)通過/phones/:phoneId
這樣的路由申明來匹配當(dāng)前的URL柬姚。:
標(biāo)記的變量值都會(huì)被抽取并且賦值給$routeParams
對(duì)象拟杉。
控制器
app/js/controllers.js
var phonecatControllers = angular.module('phonecatControllers', []);
phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',
function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}]);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',
function($scope, $routeParams) {
$scope.phoneId = $routeParams.phoneId;
}]);
可以看到,我們又創(chuàng)建一個(gè)叫phonecatControllers
的模塊量承。對(duì)于小型的AngularJS應(yīng)用來說搬设,把所有的少量的控制器放在一個(gè)模塊里時(shí)很常見的。但是當(dāng)我們的應(yīng)用越來越大撕捍,把代碼整理到新的模塊中去也是很常見的拿穴。對(duì)于大型的應(yīng)用來說,你很可能為應(yīng)用的每個(gè)較大的功能分別設(shè)計(jì)一個(gè)模塊忧风。
目前我們的應(yīng)用還很小默色,所以都放在phonecatControllers
模塊中也是可以理解的。
測(cè)試
為了自動(dòng)測(cè)試所有東西都被成功綁定了狮腿,我們需要寫一些E2E(端到端)的測(cè)試用例來驗(yàn)證不通的URL可以得到正確的視圖渲染腿宰。
...
it('should redirect index.html to index.html#/phones', function() {
browser.get('app/index.html');
browser.getLocationAbsUrl().then(function(url) {
expect(url).toEqual('/phones');
});
});
describe('Phone list view', function() {
beforeEach(function() {
browser.get('app/index.html#/phones');
});
...
describe('Phone detail view', function() {
beforeEach(function() {
browser.get('app/index.html#/phones/nexus-s');
});
it('should display placeholder page with phoneId', function() {
expect(element(by.binding('phoneId')).getText()).toBe('nexus-s');
});
});
然后通過npm run protractor
命令來觀察測(cè)試運(yùn)行呕诉。
實(shí)驗(yàn)小能手
把{{orderProp}}
數(shù)據(jù)綁定加入到index.html
,你會(huì)發(fā)現(xiàn)什么都沒變吃度,在手機(jī)列表視圖中也一樣甩挫。這是因?yàn)?code>orderProp模型的屬于PhoneListCtrl
域,對(duì)應(yīng)綁定的HTML元素是<div ng-view>
规肴。如果你把它加入到phone-list.html
模版中捶闸,就能正常工作了。
總結(jié)
我們已經(jīng)實(shí)現(xiàn)了多視圖和路由功能拖刃,下面一章我們將實(shí)現(xiàn)詳細(xì)的手機(jī)詳情視圖删壮。