能够从页面的一个视图跳转到另外一个视图,对单页面应用来讲是至关重要的。当应用变得越来越复杂时,我们需要一个合理的方式来管理用户在使用过程中看到的界面。除了用ng-include指令在视图中引用多个模板外,更好的做法是将视图分解成布局和模板视图,并且根据用户当前访问的URL来展示对应的视图。我们会将这些模板分解到视图中,并在布局模板内进行组装。 AngularJS允许我们在$route服务的提供者$routeProvider中通过声明路由来实现这个功能。通过$routeProvider,可以发挥出浏览器历史导航的优势,并让用户基于浏览器当前的URL地址创建书签或分享页面。
安装
从1.2版本开始, AngularJS将ngRoutes从核心代码中剥离出来成为独立的模块。我们需要安装并引用它,才能够在AngularJS应用中正常地使用路由功能。可以从code.angularjs.org下载它,然后保存到一个可以在HTML页面中进行引用的位置,例如js/vendor/angular-route.js。也可以用Bower来安装,这样会将它存放到Bower的目录中。
在HTML中,需要在AngularJS之后引用angular-route:
|
|
最后,要把ngRoute模块在我们的应用中当作依赖加载进来:
|
|
布局模板
要创建一个布局模板,需要修改HTML以告诉AngularJS把模板渲染到何处。通过将ng-view指令和路由组合到一起,我们可以精确地指定当前路由所对应的模板在DOM中的渲染位置:
|
|
ng-view是由ngRoute模块提供的一个特殊指令,它的独特作用是在HTML中给$route对应的图内容占位。它会创建自己的作用域并将模板嵌套在内部。
ngView指令遵循以下规则:
- 每次触发$routeChangeSuccess事件,视图都会更新。
- 如果某个模板同当前的路由相关联:
- 创建一个新的作用域
- 移除上一个视图,同时上一个作用域也会被清除
- 将新的作用域同当前模板关联在一起
- 如果路由中有相关的定义,那么就把对应的控制器同当前作用域关联起来
- 触发$viewContentLoaded事件
- 如果提供了onload属性,调用该属性所指定的函数
路由
我们可以使用AngularJS提供的when和otherwise两个方法来定义应用的路由。
用config函数在特定的模块或应用中定义路由:
现在,我们可以用when方法来添加一个特定的路由。这个方法可以接受两个参数(when(path,route)):
第一个参数是路由路径,这个路径会与$location.path进行匹配, $location.path也就是当前URL的路径。如果路径后面还有其他内容,或使用了双斜线也可以正常匹配。我们可以在URL中存储参数,参数需要以冒号开头(例如:name),后面会讨论如何用$routeParams读取这些参数。
第二个参数是配置对象,决定了当第一个参数中的路由能够匹配时具体做些什么。配置对象中可以进行设置的属性包括controller、 template、 templateURL、 resolve、 redirectTo和reloadOnSearch。
controller
controller:
MyController
或者controller: function($scope) {}
如果配置对象中设置了controller属性,那么这个指定的控制器会与路由所创建的新作用域关联在一起。如果参数值是字符型,会在模块中所有注册过的控制器中查找对应的内容,然后与路由关联在一起。如果参数值是函数型,这个函数会作为模板中DOM元素的控制器并与模板进行关联。template
template:
<div><h2>Route</h2></div>
AngularJS会将配置对象中的HTML模板渲染到对应的具有ng-view指令的DOM元素中。templateUrl
templateUrl:
views/template_name.html
应用会根据templateUrl属性所指定的路径通过XHR读取视图(或者从$templateCache中读取)。如果能够找到并读取这个模板, AngularJS会将模板的内容渲染到具有ng-view指令的DOM元素中。resolve
12345678resolve: {'data': ['$http', function($http) {return $http.get('/api').then(function success(resp) { return response.data; },function error(reason) { return false; });}];}如果设置了resolve属性, AngularJS会将列表中的元素都注入到控制器中。如果这些依赖是promise对象,它们在控制器加载以及$routeChangeSuccess被触发之前,会被resolve并设置成一个值。列表对象可以是:键,键值是会被注入到控制器中的依赖的名字;工厂,即可以是一个服务的名字,也可以是一个返回值,它是会被注入到控制器中的函数或可以被resolve的promise对象。
redirectTo
redirectTo:
/home
或者redirectTo: function(route,path,search)
如果redirectTo属性的值是一个字符串
,那么路径会被替换成这个值,并根据这个目标路径触发路由变化。如果redirectTo属性的值是一个函数
,那么路径会被替换成函数的返回值,并根据这个目标路径触发路由变化,AngularJS会在调用它时传入下面三个参数中:(1) 从当前路径中提取出的路由参数;(2) 当前路径;(3) 当前URL中的查询串。reloadOnSearch
如果reloadOnSearch选项被设置为true(默认),当$location.search()发生变化时会重新加载路由。如果设置为false,那么当URL中的查询串部分发生变化时就不会重新加载路由。这个小窍门对路由嵌套和原地分页等需求非常有用。下面的例子中设置了两个路由:一个首页路由和一个收件箱路由,同时首页路由被设置成默认路由。
123456789101112angular.module('MyApp', []).config(['$routeProvider', function($routeProvider) {$routeProvider.when('/', {controller: 'HomeController',templateUrl: 'views/home.html'}).when('/inbox/:name', {controller: 'InboxController',templateUrl: 'views/inbox.html'}).otherwise({redirectTo: '/'});}]);
$location 服务
AngularJS
提供了一个服务用以解析地址栏中的URL
,并让你可以访问应用当前路径所对应的路由。它同样提供了修改路径和处理各种形式导航的能力。location
服务对JavaScript
中的window.location
对象的API
进行了更优雅地封装,并且和AngularJS
集成在一起。当应用需要在内部进行跳转时是使用location
服务的最佳场景,比如当用户注册后、修改或者登录后进行的跳转。location
服务没有刷新整个页面的能力。如果需要刷新整个页面,需要使用$window.location
对象(window.location
的一个接口)。
path()
path()用来获取页面当前的路径:
修改当前路径并跳转到应用中的另一个URL:
path()方法直接和HTML5的历史API进行交互,所以用户可通过点击后退按钮退回到上一个页面。
replace()
如果你希望跳转后用户不能点击后退按钮(对于登录之后的跳转这种发生在某个跳转之后的再次跳转很有用), AngularJS提供了replace()方法来实现这个功能:
absUrl()
absUrl()方法用来获取编码后的完整URL:
hash()
hash()方法用来获取URL中的hash片段:
host()
host()方法用来获取URL中的主机:
port()
port()方法用来获取URL中的端口号:
protocol()
protocol()方法用来获取URL中的协议:
search()
search()方法用来获取URL中的查询串:
我们可以向这个方法中传入新的查询参数,来修改URL中的查询串部分:
url()
url()方法用来获取当前页面的URL:
如果调用url()方法时传了参数,会设置并修改当前的URL,这会同时修改URL中的路径、
查询串和hash,并返回$location。
路由模式
不同的路由模式在浏览器的地址栏中会以不同的URL格式呈现。 $location
服务默认会使用标签模式来进行路由。路由模式决定你的站点的URL长成什么样子。
标签模式
标签(hashbang)
是AngularJS用来同你的应用内部进行链接的技巧。标签模式
是HTML5模式
的降级方案,URL路径会以#符号开头
。标签模式不需要重写<a href=""></a>
标签,也不需要任何服务器端的支持。如果没有进行额外的指定, AngularJS将默认使用标签模式。
使用标签模式的URL看起来是这样的:1http://yoursite.com/#!/inbox/all
如果要显式指定配置并使用标签模式,需要在应用模块的config函数中进行配置:
我们还可以配置hashPrefix,也就是标签模式下标签默认的前缀!符号。这个前缀也是AngularJS在比较老的浏览器中降级机制的一部分。这个符号是可以配置的:
AngularJS支持的另外一种路由模式是html5模式。在这个模式中, URL看起来和普通的URL一样(在老式浏览器中看起来还是使用标签的URL)。例如,同样的路由在HTML5模式中看起来是这样的:
在AngularJS内部, location
服务通过HTML5历史API
让应用能够使用普通的URL路径来路由。当浏览器不支持HTML5历史API
时, location
服务会自动使用标签模式的URL作为替代方案。location
服务还有一个有趣的功能,当一个支持HTML5历史API
的现代浏览器加载了一个带标签的URL时,它会为用户重写这个URL。
在HTML5模式中, AngularJS会负责重写<a href=""></a>
中的链接。也就是说AngularJS会根据浏览器的能力在编译时决定是否要重写href=""
中的链接。例如<a href="/person/42?all=true">Person</a>
这个标签,在老式浏览器中会被重写成标签模式的URL: /index.html#!/person/42?all=true
。但在现代浏览器中会URL会保持本来的样子。后端服务器也需要支持URL重写,服务器需要确保所有请求都返回index.html,以支持HTML5模式。这样才能确保由AngularJS应用来处理路由。当在HTML5模式的AngularJS中写链接时,永远都不要使用相对路径
。如果你的应用是在根路径中加载的,这不会有什么问题,但如果是在其他路径中, AngularJS应用就无法正确处理路由了。另一个选择
是在HTML文档的HEAD中用
路由事件
route
服务在路由过程中的每个阶段都会触发不同的事件,可以为这些不同的路由事件设置监听器并做出响应。这个功能对于控制不同的路由事件,以及探测用户的登录和授权状态等场景是非常有用的。我们需要给路由设置事件监听器,用rootScope
来监听这些事件。$routeChangeStart
AngularJS在路由变化之前会广播routeChangeStart
事件。在这一步中,路由服务会开始加载路由变化所需要的所有依赖,并且模板
和resolve键中的promise
也会被resolve。
$routeChangeStart
事件带有两个参数:
- 将要导航到的下一个URL;
- 路由变化前的URL。
$routeChangeSuccess
AngularJS会在路由的依赖被加载后广播$routeChangeSuccess事件。
$routeChangeStart
事件带有三个参数:
- 原始的AngularJS evt对象
- 用户当前所处的路由
- 上一个路由(如果当前是第一个路由,则为undefined)
$routeChangeError
AngularJS会在任何一个promise被拒绝或者失败时广播$routeChangeError事件。
$routeChangeError
事件有三个参数:
- 当前路由的信息
- 上一个路由的信息
- 被拒绝的promise的错误信息
$routeUpdate
AngularJS在reloadOnSearch
属性被设置为false的情况下,重新使用某个控制器的实例时,会广播routeUpdate
事件。
Web爬虫对于JavaScript的胖客户端应用无能为力。为了在应用的运行过程中给爬虫提供支持,我们需要在头部添加meta标签。这个元标记会让爬虫请求一个带有空的转义片段参数的链接,服务器根据请求返回对应的HTML代码片段。
更多关于路由的内容
location
服务不会重新加载整个页面,它只会单纯地改变URL。如果我们想重新加载整个页面,需要用window
服务来设置地址。
如果我们想要在作用域的生命周期外使用location
服务,必须用apply
函数将变化抛到应用外部。因为location
服务是基于digest
来驱动浏览器的地址变化,以使路由事件正常工作的。