在这一步中,您将学到如何创建一个布局模板,并且学习怎样使用一个叫做ngRoute的Angular模块来构建一个具有多重视图的应用。
·当您现在访问/index.html,您将被重定向到/index.html#!/phones,电话列表会显示在浏览器中;
·当您点击一部电话的超链接,URL会改变至该指定电话,浏览器将展示一个简短的电话细节页面。
最大的不同列举如下,您可以点击这里在GitHub上查看全部的不同。
依赖
这一步中添加的路由功能是由Angular中的ngRoute模块提供的,该模块由核心的Angular框架独立出来。
由于我们使用Bower来安装客户端的依赖,该步中我们更新bower.json配置文件来添加新的依赖:
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.5.x",
"angular-mocks": "1.5.x",
"angular-route": "1.5.x",
"bootstrap": "3.3.x"
}
}
新的依赖"angular-route": "1.5.x"告诉bower去安装一个与Angular1.5.x版本兼容的angular-route模块版本。我们必须告诉bower去下载和安装这些依赖。
npm install
多重视图,路由和布局模板
我们的应用正在逐步变得复杂。在这一步之前,应用为我们的用户提供了一个单页视图(包括电话列表),并且所有的模板代码都位于phone-list.template.html文件下。构建应用的下一步,是添加一个能展示我们列表中每一部电话的细节的视图。
为了添加细节视图,我们将index.html转换成一个我们称之为“布局模板”的模板,一个于我们应用中所有视图都公共的模板。其他被包含在该布局模板中的“局部模板”依赖于当前的“路由”--当前展示给用户的视图。
Angular应用间的路由通过$routeProvider声明,这被$route服务所提供。该服务使得将控制器,视图模板和当前浏览器的URL捆绑起来变得容易。使用这点特性,我们可以实现深度链接,这使得我们可以充分利用浏览器的历史记录(前进和后退的导航)以及电子书签。
关于依赖注入,注入器和提供者的笔记
正如你注意到的,依赖注入在Angular中处于核心地位,所以对你来说深入了解它是很重要的。
在引导应用时,Angular创建一个注入器,这被用于找到和注入您应用中需要被用到的服务。注入器本身并不知道$http或$route这些服务做了什么。事实上,注入器甚至不知道这些服务的存在,除非它被适当的模块定义所配置。
注入器只执行了下面的步骤:
·加载您在应用中指定的模块定义;
·注册这些模块定义中定义的所有提供者;
·一旦被要求这么做,通过提供者,这作为可注入的函数中的参数,来懒惰式(lazily,需要时才加载)实例化服务和他们的依赖。
提供者是用来提供(创建)服务实例和对外配置API的对象,这可以被用来控制一个服务创建和运行时的行为。对于$route服务来说,$routeProvider提供API来允许您定义您应用中的路由。
(注意:提供者仅仅能被注入config函数,因此您不能在运行时将$routeProvider注入PhoneListController。)
Angular模块解决了从应用中移除全局变量的问题并且提供了配置注入器的方法。与AMD或require.js模块不同的是,Angular模块不会去试图解决脚本加载顺序或懒惰式脚本获取的问题。这些目标是完全独立的,并且每一个模块系统能并肩运作来实现他们的目标。
为了深入您对Angular依赖注入的理解,请看这里。
模板
$route服务经常和ngView指令结合使用。ngView指令扮演的角色是将当前路由的视图模板包含进布局模板。这使得其和我们的index.html完美契合。
app/index.html
:
<head>
...
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="app.module.js"></script>
<script src="app.config.js"></script>
...
<script src="phone-detail/phone-detail.module.js"></script>
<script src="phone-detail/phone-detail.component.js"></script>
</head>
<body> <div ng-view></div> </body>
我们添加了4个额外的<script>标签来在我们的应用中加载额外的JavaScript文件:
·angular-route.js:定义了Angular的ngRoute模块,这为我们提供路由;
·app.config.js:为我们的主模块配置提供者(参见下文);
·phone-detail.module.js:定义了一个包含phoneDetail组件的新模块;
·phone-detail.component.js:定义了一个phoneDetail组件的模型(参见下文)。
注意到我们在index.html模板中移除了<phone-list></phone-list>一行并且用一个包含ng-view属性的div来替换它。
配置一个模块
模块的.config()方法为我们提供了获取用于配置的提供者的入口。为了在我们的应用中获取定义于ngRoute的提供者,服务和指令,我们需要在我们的phonecatApp模块中将ngRoute添加为一个依赖。
app/app.module.js
:
angular.module('phonecatApp', [
'ngRoute',
...
]);
现在,除了核心的服务和指令,我们也能为我们的应用配置$route服务(使用其提供者)。为了能迅速定位配置代码,我们将其列为一个单独的文件并且添加.config后缀。
app/app.config.js
:
angular.
module('phonecatApp').
config(['$locationProvider', '$routeProvider',
function config($locationProvider, $routeProvider) {
$locationProvider.hashPrefix('!'); $routeProvider.
when('/phones', {
template: '<phone-list></phone-list>'
}).
when('/phones/:phoneId', {
template: '<phone-detail></phone-detail>'
}).
otherwise('/phones');
}
]);
通过使用.config()方法,我们请求将必要的提供者( 比如$routeProvider)注入到我们的配置函数,然后使用它们的方法来制定对应服务的行为。在这里,我们使用$routeProvider.when()和$routeProvider.otherwise()方法来指定我们的应用路由。
我们的路由定义如下:
·when('/phones'):决定将被展示的视图,当URL的哈希片段为/phones。通过指定的模板,Angular会创建一个phoneList组件的实例来管理视图。注意到这和我们在index.html使用的是相同的标记。
·when('/phones/:phoneId'):决定将被展示的视图,当URL的哈希片段为/phones/<phoneId>, <phoneId>是URL中变化的部分。管理phoneDetail组件中的视图。
·otherwise('/phones'):定义一个指向的回退路由,当没有被定义的路由于当前URL匹配。(这里会指向/phones)。
我们复用了我们已经构建的phoneList组件和一个新的“模型”phoneDetail组件。到目前为止,phoneDetail组件仅仅会展示选中电话的ID。(不是那么令人印象深刻,我们会在下一步中扩展它)。
注意到:phoneId作为路由声明的第二个参数,$route服务使用路由声明--'/phones/:phoneId'--作为一个与当前URL相匹配的模板。所有用:前缀定义的变量都被提取进入了$routeParams对象。
phoneDetail组件
我们创建了一个phoneDetail组件来处理电话细节视图。我们遵循和phoneList一样的惯例:使用一个独立的模块来创建phoneDetail模块,这在我们的phonecatApp模块中被添加为依赖。
app/phone-detail/phone-detail.module.js
:
angular.module('phoneDetail', [
'ngRoute'
]);
app/phone-detail/phone-detail.component.js
:
angular.
module('phoneDetail').
component('phoneDetail', {
template: 'TBD: Detail view for <span>{{$ctrl.phoneId}}</span>',
controller: ['$routeParams',
function PhoneDetailController($routeParams) {
this.phoneId = $routeParams.phoneId;
}
]
});
app/app.module.js
:
angular.module('phonecatApp', [
...
'phoneDetail',
...
]);
一点关于子模块依赖的笔记
phoneDetail模块依赖于ngRoute模块,以此来提供$routeParams对象,这被用于phoneDetail组件的控制器中。由于ngRoute也是主模块phonecatApp中的依赖,其服务和指令在整个应用中都是可获取的(包括phoneDetail组件)。
这意味着及时我们不为phoneDetail组件的依赖列表中引入ngRoute,我们的应用依然可以正常工作。虽然删除子模块中哪些已经在主模块中引入的依赖听上去还不错,但这却损害了我们来之不易的模块化。
此处的额外知识是:
·永远清楚描述一个子模块的所有依赖。不要依赖于任何继承于父模块的依赖。(因为父模块可能哪天就不见了。)
总结
随着路由的建立和电话列表视图的实现,让我们进入下一步来实现一个正确的电话细节列表。