在這一步,你將學習如何建立一個布局模板並且通過路由功能來構建一個具有多個視圖的應用。
請重設工作目錄:
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}). 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的伺服器上運行一下。
練習
試著在index.html上增加一個{{orderProp}}綁定,當你在手機列表視圖上時什麼也沒變。這是因為orderProp模型僅僅在PhoneListCtrl管理的範圍下才是可見的,這與<div ng-view>元素相關。如果你在phone-list.html模板中加入同樣的綁定,那麼這個綁定會按你設想的那樣被渲染出來。
總結
設定路由並實現手機列表視圖之後,我們已經可以進入步驟8來實現手機詳細資料檢視了。
以上就對AngularJS 路由和多視圖的資料整理,後續繼續補充相關知識,謝謝大家對本站的支援!