AngularJS 路由與多視圖

2022-04-15 14:34 更新

路由與多視圖

在這一步中,你將學會如何通過使用被稱為'ngRoute'的Angular模塊添加路由,創(chuàng)建一個布局模板,以及如何綁定一個具有多視圖的應用。

  • 當你導航到app/index.html上時,你將跳車到app/index.html/#/phones,而且手機列表出現(xiàn)在瀏覽器中。
  • 當你在手機鏈接上點擊時,url變成特定的手機,出現(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、注入器和提供者的提醒

如你已注意到的,依賴性注入(DI)是AngularJS的核心,所以對它的工作原理略知一二是很重要的。

在應用程序引導中,Angular創(chuàng)建了一個注入器,注入器用來尋找并注入你的應用所需要的所有的服務。注入器本身對$http$route服務是做什么的一無所知。實際上,注入器甚至不知道這些服務是否存在,除非用適當?shù)哪0宥x對它進行配置。

注入器只在以下步驟中出場:

  • 載入你在你的應用中指定的模塊定義。
  • 注冊所有的在模塊定義中定義的提供者。
  • 當被要求做這的時候,注入一個指定的函數(shù)以及一些必要的依賴性(服務),它通過它們的提供者來惰性實例化。

提供者是提供(創(chuàng)建)服務實例并且對外提供配置API的對象,API可以用來控制一個服務的創(chuàng)建和運行時行為。對于$route來說,$routeProvider對外提供API,API允許你定義針對你的應用程序的路由。

**注意:**只能夠把提供者注入到`config`函數(shù)中。因此你不能夠把`$routeProvider`注入到`PhoneListCtrl`中。

Angular模塊解決了從應用程序中移除全局狀態(tài)的問題,并提供配置注入器的方法。相對于AMD或require.js模塊,Angular模塊并不試圖解決腳本載入次序問題或者懶惰式腳本取得問題。這些目標是完全獨立的,兩個模塊系統(tǒng)可以并立存在,并實現(xiàn)他們的目標。

要想加深你對Angular上的DI的理解,請參看理解依賴性注入。

模板

$route服務常與ngView指令結(jié)合使用。ngView指令的角色是在布局模板中包含用于當前路由的視圖模板。這使它完美恰合我們的index.html模板。

**注意:**從AngularJS v1.2版開始,`ngRoute`在它自己的模塊中,必須通過載入額外的`angular-route.js`文件來載入它,我們通過上面的Bower來下載`angular-route.js`文件。

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>
TODO!

我們還為手機詳情視圖添加了一個占位符模板:

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模板上添加同樣的綁定,綁定將如你的預期運作起來。
* 在`PhoneCatCtrl`中,創(chuàng)建一個帶有`this.hero='Zoro'`的新模塊,稱為"hero"。在`PhoneListCtrl`中,讓我們用`this.hero='Batman'`來遮蔽它。在`PhoneDetailCtrl`中,我們將使用`this.hero = "Captain Proton"`。然后 把`

hero = {{hero}}

`添加到全部三個模板`index.html`、`phone-list.html`和`phone-detail.html`上。打開應用,你將看到作用域繼承以及模板屬性遮蔽做了一些奇觀。

總結(jié)

隨著路由設置成功以及手機列表視力的實現(xiàn),我們已經(jīng)準備好前往第八步 更多模板,以實現(xiàn)手機詳情視圖。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號