一、初识UI-Router
UI-Router被认为是AngularUI为开发者提供的最实用的一个模块,它是一个让开发者能够根据URL状态来组织和控制界面UI的渲染,而不仅仅只改变路由(传统AngularJS应用实用的方式)。该模块为开发者提供了很多对视图(view)额外的控制。开发者可以创建嵌套分层的视图、在同一个页面使用多个视图等更多的功能。
与传统做法使用ng-view不同的是,在UI-Router里需要使用ui-view指令。由于它基于的是操作状态而仅非URL,所以当在ui-router中处理路由和状态时,开发者更关注的是当前的状态是什么。
二、一个简单应用
Main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="js/angular.min.js"></script>
<script src="js/angular-ui-router.min.js"></script>
</head>
<body ng-app="myApp">
<h1>AngularJS Home Page (Ui-router Demonstration)</h1>
<div ui-view=""></div>
</body>
</html>
<div>
<div>
<span style="width:100px" ui-sref=".Page1"><a href="">Page-1</a></span>
<span style="width:100px" ui-sref=".Page2"><a href="">Page-2</a></span>
<span style="width:100px" ui-sref=".Page3"><a href="">Page-3</a></span>
</div>
<div>
<div ui-view=""/>
</div>
</div><strong>
</strong>
<!-- Page1.html -->
<div>
<div>
<h1>Page 1 content goes here...</h1>
</div>
</div>
<!-- Page2.html -->
<div>
<div>
<h1>Page 2 content goes here...</h1>
</div>
</div>
<!-- Page3.html -->
<div>
<div>
<h1>Page 3 content goes here...</h1>
</div>
</div>
app.js
var m1 = angular.module("myApp", ['ui.router']);
m1.config(['$stateProvider','$urlRouterProvider',function($stateProvider,$urlRouterProvider){
$urlRouterProvider.when("", "/tabPanel");
$stateProvider
.state("tabPanel", {
url : "/tabPanel",
templateUrl : "tabPanel.html"
})
.state("tabPanel.Page1", {
url : "Page1",
templateUrl : 'Page1.html'
})
.state("tabPanel.Page2", {
url : "Page2",
templateUrl : 'Page2.html'
})
.state("tabPanel.Page3", {
url : "Page3",
templateUrl : 'Page3.html'
})
}]);
三、深入剖析
为特定状态指定的模板将会放在
<div ui-view></div>
元素中,在这些模板中也可以包含自己的ui-view,
这就是在同一个路由下实现嵌套视图的方法。要定义一个路由,与传统的方法相同:使用module.config
方式,但引入的服务不是$routeProvider,
而是$stateProvider。
1.模板、模板路径、模板Provider
开发者可以在每个视图下使用如下方式来设置模板:
- template:HTML字符串,或者是返回HTML字符串的函数
- templateUrl:HTML模板的路径,或者是返回HTML模板路径的函数
- templateProvider:返回HTML字符串的函数
2.控制器
和
ngRoute
相似,开发者可以指定任何已经被注册的控制器,或者在路由里面创建一个作为控制器的函数。但如果没有定义模板,控制器将无效。3.URL
url选项将会为该应用的状态指定一个url,这样当在浏览该应用的时候便能实现深度连接的效果。例如,当用户浏览到/tabPanel
时,该应用将状态改为tabPanel,同时向 Main.html 中带有 ui-view 属性的元素中插入模板。URL参数有多个选项,因此它非常强大,开发者可以像下面这样设置最基本的参数:
$stateProvider
.state("tabPanel", {
url : "/tabPanel/:num",
template : "<h1>控制面板</h1>",
controller : function($scope, $stateParams){
$scope.num = $stateParams.num;
}
});
现在将 :num 作为URL的第二个部分,例如:访问 /tabPanel/1 ,那么 $stateParams.num
就为1($stateParams就是一个对象,{num:1})。同时也可使用不同的语法:
url : "/tabPanel/{num}"
路径必须匹配URL,与ngRoute不同的是,当用户访问到 /tabPanel/
时,上面的的路径会被激活,然而当访问到 /tabPanel
时不会被激活。路径同时也使开发者可以使用正则表达式来匹配,例如:
url : "/tabPanel/{num:[0-9]{6}}" //规定num必须为六位数字
在路径里也可以指定查询参数:
url : "tabPanel?name" /* /tabPanel?name=leo 将会被匹配 */
在上面提及使用 $stateParams 来提取在url中的不同参数。该服务的作用是处理url的不同部分。例如,当上述的 tabPanel 状态是这样时:
//当用户访问者链接时:'/tabPanel/123/messages/ascending?from=10&to=20'
url: '/tabPanel/:num/messages/{sorted}?from&to'
$stateParams
对象的值为:{ num: '123', sorted: 'ascending', from: 10, to: 20 }
4.嵌套路由
使用url参数可以实现嵌套的路由,有了嵌套路由便可在同一个模板同一个路由实现多层次的ui-view。例如,Main.html 中就是嵌套了两层路由:先在Main.html中<div ui-view></div>的位置插入了tabPanel.html模板,然后又在tabPanel.html中同样的位置插入了Page1.html、Page2.html、Page3.html模板。
5.Views视图
开发者可以在一个状态中设置多个有名称的视图。该功能在ui-router中很强大,开发者可以在同一个模板中改变和切换不同的视图。如果设置了视图选项,则该状态的‘template’,‘templateUrl’及‘templateProvider’将被忽略。如果想在路由里包含父级模板,就需要创建一个包含模板的抽象模板。例如有这样的视图:
<div>
<div ui-view="student"></div>
<div ui-view="teacher"></div>
<div ui-view="classroom"></div>
</div>
接下来就可以将创建的模板分别插入到上述 有命名的 ui-view 视图了,每个子视图可以包含自己的模板、控制器和预载入数据:
$stateProvider
.state('tabPanel', {
views : {
student : {
template : "<h1>学生课余活动</h1>",
controller : function ($scope){
//...
}
},
teacher : {
templateUrl : "teacher.html",
},
classroom : {
template : "<h1>教室一角</h1>",
}
}
})
6.抽象模板
抽象模板不能被激活,但是它的子模板可以被激活。抽象模板可以提供一个包括了多个有名子视图的模板,或者它可以传递作用域变量$scope给子模板。使用它可以在同一个url下传递自定义数据或者预载入的依赖。除了需要添加
abstract
属性外,其他设置和设定一个常规状态是相同的:$stateProvider
.state('tabPanel', {
abstract : true,
url : '/tabPanel',
template : "<div ui-view></div>"
})
.state("tabPanel.Page1", {
url : "/Page1",
templateUrl : 'Page1.html'
})
.state("tabPanel.Page2", {
url : "/Page2",
templateUrl : 'Page2.html'
})
.state("tabPanel.Page3", {
url : "/Page3",
templateUrl : 'Page3.html'
})
7.预载入Resolve
使用预载入功能,开发者可以预先载入一系列依赖或者数据,然后注入到控制器中。在 ngRoute
中 resolve
选项可以允许开发者在路由到达前载入数据保证(promise)。预载入选项需要一个对象,这个对象的key即要注入到控制器的依赖,这个对象的value为需要被载入的 factory
服务。如果传入的是字符串,ngRoute会试图匹配已经注册的服务。如果传入的是函数,该函数将会被注入,并且该函数返回的值便是控制器的依赖之一。如果该函数返回一个数据保证(promise),这个数据保证将在控制器被实例化前被预先载入并且数据会被注入到控制器中。
$stateProvider.state('home', {
resolve: {
//这个函数的值会被直接返回,因为返回值不是promise对象
person: function() {
return {
name: "Ari",
email: "ari@fullstack.io"
}
},
//这个函数返回的是promise对象, 因此它将在控制器被实例化之前载入。
currentDetails: function($http) {
return $http({
method: 'JSONP',
url: '/current_details'
});
},
//前一个promise对象也可作为依赖注入到其他数据保证中!(这个非常实用)
facebookId: function($http, currentDetails) {
$http({
method: 'GET',
url: 'http://facebook.com/api/current_user',
params: {
email: currentDetails.data.emails[0]
}
})
}
},
//定义控制器
controller: function($scope, person,
currentDetails, facebookId) {
$scope.person = person;
}
})
8.事件
和ngRoute相同的是,ui-router 服务会在不同的状态生命周期 lifecycle 里启动某些事件events。监听$scope对象便可以捕获这些事件然后采取不同的响应或者操作。如下的事件将会在
$rootScope
上触发,因此在任何$scope对象上都可以监听到这些事件。(1)状态改变事件
$scope.$on('$stateChangeStart', function(evt, toState, toParams, fromState, fromParams), {
// 如果需要阻止事件的默认行为
evt.preventDefault();
});
- $stateChangeStart 当状态改变开始的时候被触发
- $stateChangeSuccess 当状态改变成功的时候被触发
- $stateChangeError 当状态改变出现错误的时候被触发,错误通常是目标无法载入,需要预载入的数据无法被载入等。
(2)视图载入事件
- $viewContentLoading 当视图正在被载入并且DOM被渲染之前触发
$scope.$on('$viewContentLoading', function(event, viewConfig){
// 获取任何视图设置的参数,以及一个特殊的属性: viewConfig.targetView
//...
});
- $viewContentLoaded 当视图载入完成并且DOM渲染完毕时触发
9.$urlRouterProvider
和ngRoute一样,开发者可以在该对象上设定特定的URL被激活时做什么的规则。由于设定好的状态会在特定的url被访问时被激活,所以$urlRouterProvider没有必要用来管理激活和载入状态。但当需要管理那些发生在当前状态之外的作用域时它会非常有用,例如在重定向或者安全验证的时候。在模块的设置函数里便可使用$urlRouterProvider。
(1)when()
该函数需要两个参数:1.当前的路径,2.需要重定向到的路径(或者是需要在路径被访问时运行的函数)。设置重定向前需要为$urlRouterProvider设置when函数来接受一个字符串。例如,当希望重定向一个空的路由到/tabPanel:
m1.config(function($urlRouterProvider) {
$urlRouterProvider.when('', '/tabPanel');
});
如果传递的是函数,在路径被匹配时该函数会被执行,处理器返回如下3个值中的一个:
- false,该回应告诉$urlRouter没有匹配到当前url规则,应该尝试匹配新的路径,这样能保证用户访问了正常的路径。
- 字符串,$urlRouter将该字符串当做重定向的路径。
- true或者 undefined,该回应告诉$urlRouter,url已被处理。
(2)otherwise()
和ngRoute的otherwise()函数相似,在用户提交的路径没有被定义的时候它将重定向到指定的页面。这是个创建默认路径的好方法。 otherwise()只接受一个参数,即函数或者字符串,字符串必须为合法的url路由地址,函数则会在没有任何路径被匹配的时候被运行。
m1.config(function($urlRouterProvider) {
$urlRouterProvider.otherwise('/');
/*$urlRouterProvider.otherwise(
function($injector, $location) {
$location.path('/');
});*/
});
(3)rule()
如果想越过任何URL的匹配或者在其他路由前做路由修改,则可以使用rule()函数。在使用它的时候必须返回一个合法的代表路径的字符串。
m1.config(function($urlRouterProvider){
$urlRouterProvider.rule(
function($injector, $location) {
return '/index';
});
})