7-路由与更多视图

在这步,你将学习创建布局,怎么通过增加路由,使用Angular模型叫做‘ngRoute’, 创建一个应用拥有更多的视图。

  • 当你导航到 app/index.html,你会被指向 app/index.html/#/phones,手机列表会出现在浏览器中。

  • 当你点击手机链接,URL会更改到那个手机特有的,手机的详细页面会显示。

工作空间重置介绍 重置你的工作区间到第七步

git checkout -f step-7

刷新你的浏览器,或者在线上检出这一步:第七步例子。 大部分重要的修改都列在下面,你在 GitHub上能看全部不同。

依赖

在这一步,Angular中 ngRoute模块的路由功能被增加。他是从核心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.3.0",
    "angular-mocks": "~1.3.0",
    "jquery": "2.1.1",
    "bootstrap": "~3.1.1",
    "angular-route": "~1.3.0"
  }
}

新的依赖 "angular-route": "~1.3.0"告诉bower去安装兼容于1.3.x版本的angular-route组件,我们只要告诉bower下面并安装依赖。 如果你安装了全局的bower,你就可以运行 bower install,但在这个项目中,我们已经预配置npm去运行bower给我们安装。

npm install

多视图,路由和布局模版

我们的应用慢慢成长也变得更复杂。在第七步前,应用提供单个的页面给用户(列出所有的电话),所有的模板代码位于index.html文件中,下一步中建设应用是增加一个视图,用来展现我们列表中每个设备的详细信息。 为了增加一个详细的视图,我们继承 index.html文件,包含所有视图的模板代码,但是麻烦很快就来了。相反,我们要转换index.html模板成我们称为的布局模板。这个模板是我们应用所有视图公共的。依赖当前路由,其他“局部模板”被包含到这个布局模板中——视图正确显示给用户。 Angular应用的路由定义通过由 $route服务提供的 $routeProvider提供,这个服务让控制器、视图模板和当前浏览器中URL位置很容易的连在一起。使用这个特征,我们能够实现 深层连接,这样我们能利用浏览器的历史(返回与前进导航)与书签。

关于DI,注入器,提供者的注意

如你所 注意的, 依赖注入(DI)是AngularJS的核心,所以你懂得一些关于他怎么工作是很重要的。 当应用启动,Angular创建一个注入器,他将用来注入所有服务,这是你的应用必要的。注入器自己不知道 $http与$route服务是怎么工作的,实际上,他甚至不知道这些服务的存在,直到他被模板正确的定义。 注入器只会做下面的步骤:

  • 读取你指定的模块定义。

  • 注册模块中定义的所有提供者。

  • 当被寻问加载,注入指定的函数和任何需要的依赖,他会通过提供者延迟加载。

提供者是提供(创建)服务实例和暴露APIs配置的对象,他能用来控制创建和服务运行时的行为,在例子中,$route服务, $routeProvider暴露APIs,允许你定义你的应用路由。 注意:提供者只能注入config函数中,这样你不能注入$routePRoviderPhoneListCtrl中。 Angular模型解决了应用中移走全局状态的问题和提供了配置注入器的方式,相对于AMD或requie.js模块,Angular模块不会尝试解决脚本的读取顺序与延迟读取,这些目的都是独立的,都能所有的模块能并排生存,完成他们的目的。 想增加你对AngularDI的了解,看 了解依赖注入。

模板

$route服务经常与NgView联合使用,ngView命令的角色是把当前的视图包含到布局模板中,这让他完美的适配到我们index.html的模板中。 注意:从AngularJS1.2版本起,ngRoute在他自己的模块中,必须用额外的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>

在我们的index文件,我们增加了两个新 <sript>标签,去加载额外的JavaScript到我们的应用。

  • angular-route.js : 定义Angular的ngRoute模板,他提供路由给我们。

  • app.js : 这个文件现在持有我们的应用的root模块。

注意我们从 index.html中移走了大部分代码,并用有 ng-view属性的div单行替换。我们移走的代码到 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>

注意我们使用了在 PhoneDetailCtrl控制器中定义的phoneId表达式。

应用模块

为了提高我们应用的组织性,我们使用Angular的ngRoute模块,我们从控制器中移到了他们自己的模块中。(如下面的显示) 我们添加了 angualr-route.jsindex.html,在controllers.js中创建了新的phonecatControllers模块,那不是我们使用他们的代码所有需要做全部的。然而,我们给我们应用也增加了依赖模块,通过列出phonecatApp中依赖的两个模块,我们能使用他们提供的命令和服务。 app/js/app.js:

var phonecatApp = angular.module('phonecatApp', [
  'ngRoute',
  'phonecatControllers'
]);

...

注意第二个参数通过 angular.module,['ngRoute', 'phonecatControllers'],这个数据列出phonecat依赖的模块。

...

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去注入到我们的配置函数,使用$routeProvider方法定义给我们的路由。 我们的应用定义如下:

  • when('/phones'): 当URL哈希值是/phones手机列表视图将显示,为了构成这个视图,Angular将会使用phone-list.html模板和PhoneListCtrl控制器。

  • when('/phones/:phoneId'): 当URL哈希值配置'/phones/:phoneId'手机详细将会被展示,:phoneId是URL中部分变量,为了构建手机详细视图,Angular将会使用phone-detail.html模板和PhoneDetailCtrl控制器。

  • otherwise({redirectTo: '/phones'}): 当浏览器地址不匹配任何我们的路由时,就触发重定向到/phones。

我们重用了我们在前面步骤中构建的PhoneListCtrl控制器,也给手机详细视图增加了一个新的,空的 PhoneDetailCtrl控制器到 app/js/controllers.js。 注意在第二个路由定义中,我们使用了:phoneId参数。 $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;
  }]);

注意,我们创建了一个叫 phonecatControllers的模块,对于小AngularJS应用,如果只有少量控制器,通常只给所有的控制器创造一个模块。当应用增加,完全可以重构你的代码,增加模块,对于一个大型应用,你可能给你应用大特征创建分离的模块。 因为我们的例子应用相对小,我们只增加我们所有控制器到 phonecatControllers 模块。

测试

去自动验证所有都连接好,我们写端对端测试,指向可能的URL地址,验证当前视图已经渲染。

...
   it('should redirect index.html to index.html#/phones', function() {
    browser.get('app/index.html');
    browser.getLocationAbsUrl().then(function(url) {
        expect(url.split('#')[1]).toBe('/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 去看测试运行。

实验

尝试增加一个 {{orderProp}}绑定给 index.html,尽管你在手机列表中,但是会发现没有发生什么事,这是因为 orderProps模型只对 PhoneListCtrl管理的scope中可见。关联在 <div ng-view>元素上,如果你增加相同的绑定到 phone-list.html模型,这个绑定会如你所愿工作。

总结

使用路由启动手机列表已经实现,我们现在去 第8步,实现手机列表详细视图。 原文地址: https://docs.angularjs.org/tutorial/step_07

你可能感兴趣的:(7-路由与更多视图)