浅析ui-router

什么是 ui-router

不同于 ng-router 基于 url 的路由系统, ui-router 提供了一个基于 state 状态机的路由方式。它可以实现在不改变 url 情况下更新视图中局部内容,也可以在一个页面内嵌套并更新多个视图(状态的嵌套,这是相对于 ng-router 的一大优势),使用 ui-router 可以轻松应对复杂场景的页面应用。

ui-router 基本用法

state 方法会返回 $stateProvider 本身,和 ng-router 一样支持链式操作。

抽象 state

1
2
3
4
5
6
7
$stateProvider
// setup an abstract state for the tabs directive
.state('tabs', {
url: '/tab',
abstract: true,
templateUrl: 'templates/tabs.html'
})

上述代码描述了一个抽象的状态,一个抽象的状态可以有子状态但不能显式激活,它将被隐性激活当其子状态被激活时。
下面是一些你将可能会使用到抽象状态的示例:

  • 为所有子状态预提供一个基url在父状态中设置template属性,子状态对应的模板将插入到父状态模板中的ui-view(s)中
  • 通过resolve属性,为所有子状态提供解决依赖项通过data属性,为所有子状态或者事件监听函数提供自定义数据
  • 运行onEnter或onExit函数,这些函数可能在以某种方式修改应用程序。
  • 上面场景的任意组合

请记住:抽象的状态模板仍然需要,来让自己的子状态模板插入其中。因此,如果您使用抽象状态只是为了预提供基url、提供解决依赖项或者自定义data、运行onEnter/Exit函数,你任然需要设置template: ““。

上述代码中为所有子状态指定了一个基 url ,后续所有的子状态都会继承这个 url (表现形式是:/tab/childUrl)。

子 state

1
2
3
4
5
6
7
8
9
.state('tabs.questionList', {
url: '/questionList',
views: {
'questionList': {
templateUrl: 'templates/Question/questionList.html',
controller: 'QuestionController'
}
}
})

使用点语法为 tabs 状态创建了一个子状态,对应的 html 代码应该是这样的:

1
2
3
<ion-tab icon="ion-navicon" href="#/tab/questionList">
<ion-nav-view name="questionList"></ion-nav-view>
</ion-tab>

ion-nav-view 实际上它是一个具有 ui-view 指令的 html 标签。 nameui-view 的名字,tabs 状态对应的是包含了 questionList 视图的 tabs.html,questionList 嵌套在 tabs 页面内部,所以 questionList 是 tabs 的子状态。

views 中的 questionList 是视图的名称,ui-router 将会把这个视图替换成 templateUrl 中对应的 html。

controllerquestionList 视图对应的控制器。

views

一个 url 对应的页面中嵌套有多个视图的时候,可以再 views 对象中添加对应的视图名字为 key 的对象,运行时嵌套的视图会按照规则被自动替换。

1
2
3
4
5
6
7
8
9
10
11
12
13
.state('tabs.mainPage', {
url: '/questionList',
views: {
'questionList': {
templateUrl: 'templates/Question/questionList.html',
controller: 'QuestionController'
},
'answerList': {
templateUrl: 'templates/Answer/answerList.html',
controller: 'AnswerController'
}
}
})

对应的 html :

1
2
<div ui-view="questionList"></div>
<div ui-view="answerList"></div>

resolve

为子状态注入依赖,参见:http://bubkoo.com/2014/01/01/angular/ui-router/guide/nested-states%20&%20nested-views/#继承解决的依赖项

页面跳转

页面跳转可以使用 $state.go(stateName [,paramObject] [,option]) 第三个参数一般不使用,具体 api 参考请见
https://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state#methods_go

例子:

1
2
3
4
$state.go('tabs.discovery', { anId: anId });//去到绝对state路径
$state.go('^.discovery', { anId: anId });//去姊妹状态
$state.go('^');//回父状态
$state.go('.child.grandchild');//去孙状态

或者可以使用 ui-sref 指令在 html 中直接写 state 跳转。

1
2
3
<li ui-sref-active="current"><a ui-sref="home.index">1</a></li>
<li ui-sref-active="current"><a ui-sref="home.project">2</a></li>
<li ui-sref-active="current"><a ui-sref="^.project.home({id:3})">3</a></li>

ui-sref-active 指令将在元素被选中的时候,为元素添加 active 的 class。

实战

tab 单页面应用

对于一个拥有两个 tab 的单页面应用来说,每一个 tab 都对应着一个自己的视图,每一个 tab 都应该拥有一个自己的导航栈。但对于 ui-router 而言,我们始终是在替换同一个视图的内容,所以所有的页面都要挂在 tab 的子级,而不是更下方的层次。

公共页面的处理

从不同 tab 中的页面跳转至相同页面情况存在时,需要将公共页面作为 tab 的子 state,分别在需要跳转的 tab 下使用不同的名称挂载,url 也需要不同,类似如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 问题列表state
.state('tabs.questionList', {
url: '/questionList',
views: {
'questionList': {
templateUrl: 'templates/Question/questionList.html',
controller: 'QuestionController'
}
}
})
// 问题详情
.state('tabs.questionList-question-detail', {
url: '/questionList/question/:quId',
views: {
'questionList': {
templateUrl: 'templates/Public/question-detail.html',
controller: 'QuestionDetailCtrl'
}
}
})
// 答案详情
.state('tabs.questionList-answer-detail', {
url: '/questionList/answer/:anId',
views: {
questionList: {
templateUrl: 'templates/Public/answer-detail.html',
controller: 'AnswerDetailController'
}
}
})


//发现
.state('tabs.discovery', {
url: '/discovery',
views: {
'discovery': {
templateUrl: 'templates/Discovery/discovery.html',
controller: 'DiscoveryController'
}
}
})
.state('tabs.discovery-question-detail',{
url:'/discovery/question/:quId',
views: {
'discovery': {
templateUrl: 'templates/Public/question-detail.html',
controller: 'QuestionDetailCtrl'
}
}
})
.state('tabs.discovery-answer-detail',{
url:'/discovery/answer/:anId',
views: {
'discovery': {
templateUrl: 'templates/Public/answer-detail.html',
controller: 'AnswerDetailController'
}
}
})

这里的页面逻辑是,问题列表->问题详情->答案详情&发现->问题详情|发现->答案详情。我们就需要按照如上方式进行 state 的配置。