在這一步中,你將學會如何通過使用被稱為'ngRoute'的Angular模塊添加路由,創(chuàng)建一個布局模板,以及如何綁定一個具有多視圖的應用。
app/index.html
上時,你將跳車到app/index.html/#/phones
,而且手機列表出現(xiàn)在瀏覽器中。把工作空間重置到第七步
git checkout -f step-7
刷新你的瀏覽器或在線檢查這一步:Step 7 Live Demo
下面列出了第六步和第七步之間的區(qū)別。你可以在GitHub里看到完整的差異。
這一步中添加路由功能是由 ngRoute
模塊中的angular提供的,它與核心的Angular框架分離分布。
我們使用Bower以安裝客戶端依賴性。這一步更新了bower.json
配置文件,以包含新的依賴性:
{
"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.1.1",
"bootstrap": "~3.1.1",
"angular-route": "1.4.x"
}
}
新的依賴性"angular-route": "1.4.x"
告訴bower要安裝與v1.4x兼容的angular-router組件版本。我們將告訴bower以下載并安裝該依賴性。
如果你已經(jīng)全局安裝了bower,則你可以只對該項目運行bower install
,我們已經(jīng)預配置了npm,從而為我們運行bower安裝:
npm install
我們的應用漸漸地完善,變得越來越復雜。在第七步之前,應用向我們用戶提供了單一視圖(手機的列表),而且所有的模板代碼都位于index.html
文件中。構(gòu)建應用的下一步是添加一個視圖,這個視圖將顯示我們的列表中每款設備的詳細信息。
要想添加詳情視圖,我們可以擴展index.html
以包含兩套視圖的模板代碼,但是那將很快變得混亂。因此我們不用這種方法,而是把index.html
變成“布局模板”。這是一個模板,常用于我們應用中的所有視圖。然后別的“局部布局模板”根據(jù)當前的“路由”包含到這個布局模板中,從而形成一個完整視圖展示給用戶。
通過$routeProvider來聲明Angular中的應用程序路由,它是$route服務的提供者。這個服務使接通控制器、視圖模板以及瀏覽器中的當前位置變得容易。利用這個功能,我們可以實現(xiàn)深鏈接,深鏈接讓我們可以使用瀏覽器的歷史(回退和前進導航)以及書簽。
如你已注意到的,依賴性注入(DI)是AngularJS的核心,所以對它的工作原理略知一二是很重要的。
在應用程序引導中,Angular創(chuàng)建了一個注入器,注入器用來尋找并注入你的應用所需要的所有的服務。注入器本身對$http
或$route
服務是做什么的一無所知。實際上,注入器甚至不知道這些服務是否存在,除非用適當?shù)哪0宥x對它進行配置。
注入器只在以下步驟中出場:
提供者是提供(創(chuàng)建)服務實例并且對外提供配置API的對象,API可以用來控制一個服務的創(chuàng)建和運行時行為。對于$route
來說,$routeProvider
對外提供API,API允許你定義針對你的應用程序的路由。
Angular模塊解決了從應用程序中移除全局狀態(tài)的問題,并提供配置注入器的方法。相對于AMD或require.js模塊,Angular模塊并不試圖解決腳本載入次序問題或者懶惰式腳本取得問題。這些目標是完全獨立的,兩個模塊系統(tǒng)可以并立存在,并實現(xiàn)他們的目標。
要想加深你對Angular上的DI的理解,請參看理解依賴性注入。
$route
服務常與ngView指令結(jié)合使用。ngView
指令的角色是在布局模板中包含用于當前路由的視圖模板。這使它完美恰合我們的index.html
模板。
app/index.html
:
<!doctype html>
<html lang="en" ng-app="phonecatApp">
<head>
...
<script src="/attachments/image/wk/angularjs/angular.js"></script>
<script src="/attachments/image/wk/angularjs/angular-route.js"></script>
<script src="/attachments/image/wk/angularjs/app.js"></script>
<script src="/attachments/image/wk/angularjs/controllers.js"></script>
</head>
<body>
<div ng-view></div>
</body>
</html>
我們已經(jīng)在我們的索引文件添加了兩個新的<script>
標記,從而把外部JavaScript文件載入到我們的應用程序中:
angular-route.js
: 定義Angular ngRoute
模塊,ngRoute
模塊向我們提供了路由。app.js
: 現(xiàn)在這個文件控住了我們的應用程序的根模塊。注意:我們刪除了index.html
模板中的大部分代碼,把它替換成一行代碼,包含了一個帶有元素屬性ng-view
的div。我們已經(jīng)移除的這個代碼被放到了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>
我們還為手機詳情視圖添加了一個占位符模板:
app/partials/phone-detail.html
:
TBD: detail view for <span>{{phoneId}}</span>
注意,我們正在使用的phoneId
表達式將在PhoneDetailCtrl
控制器中定義。
要想增強應用的組織,我們動用了Angular的ngRoute
模塊,我們已經(jīng)把控制器移到它們自己的模塊phonecatControllers
中(如下所示)。
我們給index.html
添加angular-route.js
,并在controllers.js
中創(chuàng)建一個新的phonecatControllers
模塊。然而,要想使用它們的代碼,我們需要做的不止于此。我們還需要添加模塊,作為我們的應用的依賴性。通過把兩個應用作為phonecatApp
的依賴性列表,我們可以使用這些指令以及它們提供的服務。
app/js/app.js
:
var phonecatApp = angular.module('phonecatApp', [
'ngRoute',
'phonecatControllers'
]);
...
注意第二個參數(shù)傳遞到angular.module
,['ngRoute','phonecatControllers']
。這個數(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()
方法以定義我們的路由。
我們的應用程序路由定義如下:
when('/phones')
:當URL映射段為/phones
的時候。將展示這個手機列表視圖。要想構(gòu)造這個視圖,Angular將使用phone-list.html
模板,以及PhoneListCtrl
控制器。when('/phones/:phoneId')
:當URL映射段匹配/phones/:phoneId
的時候(其中:phoneId
是URL的變量部分),將展示手機詳情視圖。要想構(gòu)造手機詳情視圖,Angular將使用phone-detail.html
模板以及PhoneDetailCtrl
控制器。otherwise({redirectTo: '/phones'})
:當瀏覽器的地址不匹配我們別的路由的時候,觸發(fā)一個重定向到/phones
。我們再次使用我們在上一步中構(gòu)造的PhoneListCtrl
控制器,并為手機詳情視圖向app/js/controllers.js
文件添加了一個新的、空的PhoneDetailCtrl
控制器。
注意在第二個路由聲明中:phoneId
參數(shù)的使用。$route
服務使用route聲明'/phones/:phoneId'
作為匹配當前URL的模板。所有用:
記號法定義的變量都會提取出來,放到?$routeParams
對象上。
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)建了一個新的模塊,稱為phonecatControllers
。對于小型的AngularJS應用,通常針對所有的控制器只創(chuàng)建一個模板,如果控制器只有為數(shù)不多的幾個。隨著你的應用程序擴大,常常要把你的代碼重構(gòu)到額外的模塊中。為了更大的應用,你可能將會想要為你的應用的所有的主要功能創(chuàng)建獨立的模塊。
因為我們的應用比較小,我們將把我們所有的控制器添加到phonecatControllers
模塊中。
要想自動核查所有東西都正確連通了,我們編寫了一個端到端的測試,導航到不同的URL上,并核查是否呈現(xiàn)了正確的視圖。
...
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');
});
});
你現(xiàn)在可以再次運行npm run protractor
來查看測試的運行。
index.html
的{{orderProp}}
,而且你將看到什么事也沒有發(fā)生,哪怕你正在手機列表視圖中。這是因為orderProp
模塊只有在PhoneListCtrl
管理的作用域內(nèi)是可見的,PhoneListCtrl
與<div ng-view>
元素關聯(lián)。如果你在phone-list.html
模板上添加同樣的綁定,綁定將如你的預期運作起來。隨著路由設置成功以及手機列表視力的實現(xiàn),我們已經(jīng)準備好前往第八步 更多模板,以實現(xiàn)手機詳情視圖。
更多建議: