多重视图和路由
相关概念理解
路由的作用,简单的概括就是基于View和Url的对应关系,处理跳转页面。
AngularJS 路由允许我们通过不同的 URL 访问不同的内容。
通过 AngularJS 可以实现多视图的单页Web应用(single page web application,SPA)。
通常我们的URL形式为 http://runoob.com/first/page ,但在单页Web应用中 AngularJS 通过 # + 标记 实现,例如:
http://runoob.com/#/first
http://runoob.com/#/second
http://runoob.com/#/third
当我们点击以上的任意一个链接时,向服务端请的地址都是一样的 (http://runoob.com/)。 因为 # 号之后的内容在向服务端请求时会被浏览器忽略掉。 所以我们就需要在客户端实现 # 号后面内容的功能实现。
AngularJS 路由 就通过 # + 标记
帮助我们区分不同的逻辑页面并将不同的页面绑定到对应的控制器上。
在以上图形中,我们可以看到创建了两个 URL: /first 和 /second。每个 URL 都有对应的视图和控制器。
路由ngRoute
ngRoute
即是AngularJS的路由模块。在使用ngRoute的时候,实际上,我们是在应用的主模块中引入ngRoute
模块并添加$routeProvider
服务到主模块的config方法中来达到我们的目标。
$routeProvider
用于在主应用主模块的配置方法中。$route
与$routeParams
一般常见于控制器中。
使用
- 使用Bower安装
bower install [email protected]
- 在HTML中引入ngRoute
<script src="angular.js" ></script>
<script src="angular-route.js" ></script>
- 在应用主模块中,引入ngRoute
myApp = angular.module('app', ['ngRoute']);
4. 在模块的config中绑定URL路径与控制器
config(['$routeProvider', function($routeProvider) {
// 在这里定义路由
}]);
模块组件详解
ngView
指令,提供不同路由模板插入的视图层。它会创建自己的作用域并将模板嵌套在内部。ng-view是一个优先级为1000的终极指令。ngView指令遵循以下规则:
每次触发
$routeChangeSuccess
事件,视图都会更新。如果某个模板同当前的路由相关联:
创建一个新的作用域;
- 移除上一个视图,同时上一个作用域也会被清除;
- 将新的作用域同当前模板关联在一起;
- 如果路由中有相关的定义,那么就把对应的控制器同当前作用域关联起来;
- 触发
$viewContentLoaded
事件; - 如果提供了onload属性,调用该属性所指定的函数。
$routeProvider
提供路由配置。有以下两个成员函数:
otherwise(params)
:设定映射信息到$route.current
,一般用于指定没有标明的路由如何处理。when(path, route)
:向$route
服务添加新的路由。path是指定的URL路径,route是路由映射信息。路由配置对象语法规则如下:
$routeProvider.when(url, {
template: string,
templateUrl: string,
controller: string, function 或 array,
controllerAs: string,
redirectTo: string, function,
resolve: object<key, function>
});
路由映射信息route包含的选项:
- controller:字符串或函数,指定控制器。在当前模板上执行的controller函数,生成新的scope。
- controllerAs:一个用于控制器的标识符名称。。
- template:字符串或函数,html模板。如果我们只需要在 ng-view 中插入简单的 HTML 内容,则使用该参数。
- templateUrl:字符串或函数,html模板的地址。如果我们只需要在 ng-view 中插入 HTML 模板文件,则使用该参数。
resolve:对象,一个应该注入控制器的可选的映射依赖关系。如果任何一个依赖关系是承诺,则路由将等该承诺被解决/拒绝后才实例化控制器。若所有promise都成功resolve,则resolved promise的值将注入,并触发
$routeChangeSuccess
事件。若任一promise被reject,则触发$routeChangeError
事件。映射Object为:key – {string},factory - {string|function}key
– {string}: 将被注入到控制器的依赖名。factory
- {string|function}: 若为字符串,则是服务的别名。若为函数,则被注入,且返回值被当作依赖。若结果为promise,则被resolve之后,将值注入到控制器中。请注意ngRoute.$routeParams
在resolve时依然是前一个路由。使用$route.current.params
访问新路由的参数。
resolve: {
'data': ['$http', function($http) {
return $http.get('/api').then(
function success(resp) { return response.data; },
function error(reason) { return false; }
);
}];
}
在上面的例子中,resolve会发送一个$http
请求,并将data的值替换为返回结果的值。列表中的键data会被注入到控制器中,所以在控制器中可以使用它。
- redirecTo:字符串或者函数,URL重定向。
- reloadOnSearch:boolean.如果reloadOnSearch选项被设置为true(默认),当
$location.search()
发生变化时会重新加载路由。如果设置为false,那么当URL中的查询串部分发生变化时就不会重新加载路由。这个小窍门对路由嵌套和原地分页等需求非常有用。
path路径配置:
- path可以包含以冒号开始的命名组(:name)。匹配到下一个斜杠为止的所有字符,并在路由匹配时以给定的名字存储到
$routeParams
中。 - path可以包含以冒号开始,以星号结束的命名组(:name*)。在路由匹配时,所有字符都以给定名字贪婪存储到
$routeParams
中。 - path可以包含可选的命名组,包含一个问号(:name?)。
例如,形如/color/:color/largecode/:largecode*\/edit的路由,将会匹配/color/brown/largecode/code/with/slashs/edit,并提取出:
- color: brown
- largecode: code/with/slashs.
$route
用于构建各个路由的url、view、controller这三者的关系。
方法:
- reload();
使路由服务重新加载当前路由,即使路由没有改变。 - updateParams(newParams);
操作路由服务更新当前的URL,使用newParams里指定的路由参数指定当前路由参数。
newParams:将URL参数名称映射到value。
事件:
* $routeChangeStart
URL路由开始变化(未跳转成功)的时候触发的事件。
参数 | 类型 | 描述 |
---|---|---|
event | object |
合成的事件对象。 |
next | Route |
将跳转的route信息。 |
current | Route |
当前route信息。 |
* $routeChangeSuccess
URL路由变化成功的时候触发的事件。
参数 | 类型 | 描述 |
---|---|---|
event | object |
合成的事件对象。 |
current | Route |
当前route信息。 |
previous | Route |
上一个route信息。 |
* $routeChangeError
URL路由变化失败的时候触发的事件。
参数 | 类型 | 描述 |
---|---|---|
event | object |
合成的事件对象。 |
current | Route |
当前route信息。 |
previous | Route |
上一个route信息。 |
rejection | Route |
拒绝承诺,通常是失败承诺的错误。 |
* $$routeUpdate
当承诺被拒绝时广播。
参数 | 类型 | 描述 |
---|---|---|
event | object |
合成的事件对象。 |
current | Route |
当前route信息。 |
$routeParams
解析返回路由中带有的参数。
如果我们设置下面这样的路由:
$routeProvider
.when('/inbox/:name', {
controller: 'InboxController',
templateUrl: 'views/inbox.html'
});
AngularJS会在$routeParams
中添加一个名为name的键,它的值会被设置为加载进来的URL中的值。
如果浏览器加载/inbox/all这个URL,那么$routeParams
对象看起来会是下面这样:
{ name: 'all' }
需要注意,如果想要在控制器中访问这些变量,需要把$routeParams
注入进控制器:
app.controller('InboxController', function($scope,$routeParams) {
// 在这里访问$routeParams
});
ngRoute使用代码
<div ng-app="Demo" ng-controller="testCtrl as ctrl">
<a href="#/index/page1">page1</a> - <a href="#/index/page2">page2</a> - <a href="javascript:void(0)" ng-click="ctrl.reload()">reload</a> - <a href="lavascript:void(0)" ng-click="ctrl.update()">update</a>
<div ng-view></div>
<script type="text/ng-template" id="page1.tpl">
this is page1.{{fisrtCtrl.value}}
</script>
<script type="text/ng-template" id="page2.tpl">
this is page2.{{secondCtrl.value}}
</script>
</div>
(function () {
angular.module("Demo", ["ngRoute"])
.config(["$routeProvider",routeConfig])
.controller("testCtrl", ["$route","$scope",testCtrl])
.controller("firstPageCtrl",firstPageCtrl)
.controller("secondPageCtrl",secondPageCtrl);
function routeConfig($routeProvider){
$routeProvider.otherwise("/index/page1");
$routeProvider
.when("/index/page1",{
templateUrl:"page1.tpl",
controller:"firstPageCtrl",
controllerAs:"fisrtCtrl"
})
.when("/index/page2",{
templateUrl:"page2.tpl",
controller:"secondPageCtrl",
controllerAs:"secondCtrl"
});
};
function testCtrl($route,$scope) {
this.reload = function(){
$route.reload();
};
this.update = function(){
$route.updateParams({name:"beast"});
};
$scope.$on("$routeChangeStart",function(event,nextRoute,currentRoute){
//event.preventDefault(); //可控制不跳转页面,主要在路由权限控制的时候用的多
console.log(nextRoute,currentRoute);// 下一个路由信息和上一个路由信息
});
};
function firstPageCtrl(){
this.value = "hello world";
console.log("this is page1");//用于证明reload
}
function secondPageCtrl(){
this.value = "Hello World";
console.log("this is page2");//用于证明reload
}
}());
这里直接使用了ng-template把两个模板写在一个页面,在实际使用中,可以把两个模板分开分别放到两个不同的html文件中,并且放到一个规定的文件中,这样可方便于管理。
$location详解
$location
服务:
- 暴露当前地址栏的URL,这样你就能获取并监听URL,改变URL。
- 当出现以下情况时同步URL:改变地址栏、点击了后退按钮(或者点击了历史链接)、点击了一个链接
- 一系列方法来获取URL对象的具体内容(protocol, host, port, path, search, hash)
在URL改变时,不要刷新整个页面。一定要的话,用低级的API$window.location.href
。
$location
方法
$location
服务为URL只读部分(absUrl, protocol, host, port)提供读方法,为可读写部分(url, path, search, hash)提供读写方法。
方法 | 描述 |
---|---|
absUrl() | 获取完整的url路径 |
protocol(); | 获取协议 |
host(); | 获取主机名 |
port(); | 获取主机端口 |
url([url], [replace]); | 获取当前url的#后面的内容,包括参数和哈希值 |
path([path]); | 获取当前url的子路径(也就是当前url#后面的内容,不包括参数)。 |
search(search,[paramValue]); | 获取参数的json对象 |
hash([hash]); | 获取url的哈希值 |
replace(); | 替换上一次浏览记录。如果被调用,当前$digest 过程中所有$location 的变化将取代当前的历史记录,而不是增加新的历史记录。 |
state([state]); | 返回当前url的历史状态对象(不包括任何参数)。调用一个参数并且返回$location 时改变历史状态对象。 |
所有的写方法返回同一个$location
对象来支持链式风格。比如,要在一条语句中改变URL的多个部分:
$location.path('/newValue').search({key: value});
// 假定原来的完整url为:
// http://localhost:63342/test/templates/demo01.html#/tabs/chat?name=JK&age=10#noHash
var url = $location.url("url2").absUrl();
// 到这里变成:
// http://localhost:63342/test/templates/demo01.html#/url2
url = $location.path("path2")
.search({
name: "CL",
age: 12
})
.hash('hash2')
.absUrl();
// 到这里变成:
// http://localhost:63342/test/templates/demo01.html#/path2?name=CL&age=12#hash2
$location
事件
$locationChangeStart
:在url将要被改变时广播。可以使用preventDefault取消默认事件。$locationChangeSuccess
:在url成功的完成变化后广播。
参数 | 类型 | 描述 |
---|---|---|
angularEvent | Object |
合成事件对象。 |
newUrl | string |
新的url。 |
oldUrl | string |
改变前的url。 |
newState | string |
新的历史状态对象。 |
oldState | string |
改变前的历史状态对象。 |
使用代码
(function(){
angular.module('Demo', [])
.controller('testCtrl',["$location","$scope",testCtrl]);
function testCtrl($location,$scope) {
var url = "http://example.com:8080/#/some/path?foo=bar&baz=xoxo#hashValue";
$location.absUrl();// http://example.com:8080/#/some/path?foo=bar&baz=xoxo#hashValue
$location.url();// some/path?foo=bar&baz=xoxo
$location.protocol();// http
$location.host();// example.com
$location.port();// 8080
$location.path();// /some/path
$location.search();// {foo: 'bar', baz: 'xoxo'}
$location.search('foo', 'yipee');
$location.search();// {foo: 'yipee', baz: 'xoxo'}
$location.hash();// #hashValue
$scope.$on("$locationChangeStart", function () {
//监听url变化,在变化前做想要的处理
});
$scope.$on("$locationChangeSuccess", function () {
//监听url变化,在变化后做想要的处理
});
}
}());
$location
在日常开发中是很有用的,特别是$locationChangeStart
和$locationChangeSuccess
,在做是否登录判断的时候配合拦截器使用,根据拦截器返回的状态,监听url变化并在需要登录的时候退出到登录页面。
replace()方法
$location
服务有一个特殊的replace方法可以用来告诉$lacation
服务下一次自动和浏览器同步,上一条浏览记录应该被替换而不是创建一个新的。这在重定向的时候很好用。不这样的话容易使后退按钮失效(点后退时会又触发重定向)。要改变URL而不添加新的历史记录,你可以这样做:
$location.path('/someNewPath').replace();
注意写方法并不会马上更新window.location
,而是在作用域的$digest
阶段将多个$location
操作合并成一个对windiow.location
对象的commit
操作。因为多个操作会后对浏览器来说都会只是一个,所以只要调用一次replace()方法就能实现浏览器记录的替换操作。一旦浏览器更新了,$location
服务就会将replace方法的标志重置,以后的改变就会创建新的历史记录,直到再次调用replace方法。
$location
服务配置
要想配置$location
服务,应该获取$locationProvider
对象,然后按下面的方法设置它的参数:
html5Mode(模式): {boolean}
true - 参见下文中的“HTML5模式”
false - 参见下文中的“Hashbang模式”
默认值: falsehashPrefix(前缀): {string}
Hashbang的URL前缀(用于Hashbang模式或者使用Html5模式但运行在老式浏览器中)
默认值: “”(空字符串)
范例配置
$locationProvider.html5Mode(true).hashPrefix('!');
$location.path()与!或/前缀
Path永远会以正斜杠(/)开头,$location.path()
作为setter方法调用时,如果没有用正斜杠开头,它会给加上一个。
注意,在hashbang模式中,!前缀并不是$location.path()
的一部分;它只是hash“前缀”。
写方法和字符编码
你可以给$location
服务传递特殊字符,它会根据 RFC 3986 规则来编码。当你调用写方法时:
- 所有传递给写方法(如path(), search(), hash())的值都会被编码。
- 读方法(path(), search(), hash()不带参数的调用)返回解码后的值。
- 当你调用absUrl()时,会返回各部分经过了编码的完整url。
- 当你调用url()时,返回的值是path, search 和hash,形式是/path?search=a&b=c#hash,各个组成部分也都是编码过的。
$location的双向绑定
因为 $location
使用getters/setters, 你可以使用 ng-model-options="{ getterSetter: true }"
来将它绑定到 ngModel.
<div ng-controller="LocationController">
<input type="text" ng-model="locationPath" ng-model-options="{ getterSetter: true }" />
</div>
angular.module('locationExample', [])
.controller('LocationController', ['$scope', '$location', function($scope, $location) {
$scope.locationPath = function(newLocation) {
return $location.path(newLocation);
};
}]);
Hashbang和HTML5模式
$location
服务有两种配置模式,它将控制浏览器地址栏中的地址显示格式:Hashbang模式(默认)和HTML5模式, 后者基于HTML5的历史(history)API。无论哪种模式,应用层都使用相同的API, $location
服务会自己找出合适的URL格式和浏览器API,来完成修改浏览器地址和进行历史管理的工作。
Hashbang模式 | HTML5模式 |
---|---|
配置项 | 默认 |
URL格式 | 在所有浏览器中使用hashbang格式 |
<a href=""> link重写 |
否 |
需要服务端配置 | 否 |
Hashbang模式(缺省模式)
在这种模式中,$location
在所有浏览器中都使用Hashbang地址.
it('显示范例', inject(
function($locationProvider) {
$locationProvider.html5Mode(false);
$locationProvider.hashPrefix('!');
},
function($location) {
// 打开http://example.com/base/index.html#!/a
$location.absUrl() == 'http://example.com/base/index.html#!/a'
$location.path() == '/a'
$location.path('/foo')
$location.absUrl() == 'http://example.com/base/index.html#!/foo'
$location.search() == {}
$location.search({a: 'b', c: true});
$location.absUrl() == 'http://example.com/base/index.html#!/foo?a=b&c'
$location.path('/new').search('x=y');
$location.absUrl() == 'http://example.com/base/index.html#!/new?x=y'
}
));
HTML5模式
在HTML5模式中,$location
服务的getter和setter方法通过HTML5 history API与浏览器地址栏互动, 它允许你使用标准的URL path和search格式来代替等价的hashbang模式。如果浏览器不支持HTML5 history API, 那么$location
服务将自动退回到使用hashbang URL。这将把你从担心用户的浏览器是否支持history API中解放出来, $location
服务将会透明的帮你选择最佳的可用形式。
- 在一个老式浏览器中使用标准url格式 -> 重定向到hashbang URL
- 在一个现代浏览器中使用hashbang格式 -> 用标准URL形式进行重写
it('显示范例', inject(
function($locationProvider) {
$locationProvider.html5Mode(true);
$locationProvider.hashPrefix('!');
},
function($location) {
// 在支持HTML5历史API的浏览器中:
// 打开 http://example.com/#!/a -> 将被重写为 http://example.com/a
// (替换 http://example.com/#!/a 的历史记录)
$location.path() == '/a'
$location.path('/foo');
$location.absUrl() == 'http://example.com/foo'
$location.search() == {}
$location.search({a: 'b', c: true});
$location.absUrl() == 'http://example.com/foo?a=b&c'
$location.path('/new').search('x=y');
$location.url() == 'new?x=y'
$location.absUrl() == 'http://example.com/new?x=y'
// 在不支持HTML5历史API的浏览器中:
// 打开 http://example.com/new?x=y -> 将被重定向到 http://example.com/#!/new?x=y
// (替换 http://example.com/new?x=y 的历史记录)
$location.path() == '/new'
$location.search() == {x: 'y'}
$location.path('/foo/bar');
$location.path() == '/foo/bar'
$location.url() == '/foo/bar?x=y'
$location.absUrl() == 'http://example.com/#!/foo/bar?x=y'
}
));
$location.hash([hash])和$anchorScroll
$location
的hash方法填入一个DOM元素的id值,可以快速定位到该DOM元素。
在普通的html网页中,我们可以通过在url后边添加 #elementid
的方式,将页面显示定位到某个元素,也就是锚点。
但是在angularjs应用的网页中,页面路由的写法是 #route/route
。锚点的写法会被当做一个页面路由解析过去,达不到定位的目的。
angular提供一个$anchorScroll
服务用来做锚点的功能。$anchorScroll
监听$location.hash()
并且滚动到url指定的锚点的地方。可以通过$anchorScrollProvider.disableAutoScrolling()
禁用。
<div id="scrollArea" ng-controller="ScrollController">
<a ng-click="gotoBottom()">Go to bottom</a>
<a id="bottom"></a> You're at the bottom!
</div>
angular.module('anchorScrollExample', [])
.controller('ScrollController', ['$scope', '$location', '$anchorScroll',
function ($scope, $location, $anchorScroll) {
$scope.gotoBottom = function() {
// set the location.hash to the id of
// the element you wish to scroll to.
$location.hash('bottom');
// call $anchorScroll()
$anchorScroll();
};
}]);