Angular路由Ui-Router详解

UI-Router

UI-Router是Angular第三方开发用于管理UI与路由的模块,功能比Angular原生的ng-Route要强而且全,因此原生基本已经被其替代。

UI-Router是采用的是一种状态管理机制,“状态”可以继承,“状态”不禁包含url,还有views,controller等,以此来组织路由和控制界面UI的渲染,而不是单纯的改变路由。对于UI方面提供嵌套视图,可以一个页面嵌套多个视图,多视图再嵌套单个视图,每个视图可提供其特定的controller等针对管理,方便视图重用且功能精细,可以打造十分复杂的web应用。

基本使用

导入模块angular-ui-router.js,可以通过bower包管理器或github等下载。

1
<script src="angular-ui-router/release/angular-ui-router.js"></script>

然后在ngApp中加入ui.router

1
var app = angular.module('myApp', ['ui.router']);

然后再创建login.html用于嵌套,再在需要嵌套的页面配置

1
2
// index.html url'/'
<div ui-view></div>

再配置路由

1
2
3
4
5
6
7
8
app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('login', {
url: '/',
templateUrl: 'login.html',
});
});

当进入主页它便自动将login.html嵌套到ui-view中。

指令

ui-view

ui-view是用来告诉ui-router哪里需要嵌套模板,视图可以未命名或命名。在任何模板(或根html)中只能有一个未命名的视图。

1
2
3
<div ui-view></div>
<div ui-view="chart"></div>
<div ui-view="data"></div>

在同一模板中具有多个视图时可以如下设置

1
2
3
4
5
6
7
8
9
10
11
12
13
$stateProvider.state("home", {
views: {
"": {
template: "<h1>HELLO!</h1>"
},
"chart": {
template: "<chart_thing/>"
},
"data": {
templateUrl: 'tplUrl.html'
}
}
})

autoscroll

ui-view元素指令上使用的属性指令。它允许在填充视图时设置浏览器窗口的滚动行为。默认情况下,$anchorScroll被ui-router的自定义滚动服务$uiViewScroll覆盖。

1
<ui-view autoscroll[='expression']/>

添加autoscroll属性到ui-view元素。(可选)使用表达式设置滚动是打开还是关闭。

ui-sref

绑定在<a>标签,用于状态转换,类似<a>href属性。如果状态具有关联的URL,底层通过$state.href()方法自动生成和更新属性。

用法

  • ui-sref=’stateName’
  • ui-sref=’stateName({param: value, param: value})’

例子

1
2
3
4
5
6
7
<a ui-sref="home">Home</a> | <a ui-sref="about">About</a>
<ul>
<li ng-repeat="contact in contacts">
<a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
</li>
</ul>

生成后的HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
<a href="#/home" ui-sref="home">Home</a> | <a href="#/about" ui-sref="about">About</a>
<ul>
<li ng-repeat="contact in contacts">
<a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
</li>
<li ng-repeat="contact in contacts">
<a href="#/contacts/2" ui-sref="contacts.detail({ id: contact.id })">Alice</a>
</li>
<li ng-repeat="contact in contacts">
<a href="#/contacts/3" ui-sref="contacts.detail({ id: contact.id })">Bob</a>
</li>
</ul>

ui-sref-active

ui-sref一起使用的指令,在相关ui-sref指令的状态为活动时向元素添加类,并在非活动时删除它们。主要用例是通过使“活动”状态的菜单按钮显示不同,将其与非活动菜单项区分开,来简化依赖于ui-sref的导航菜单的特殊外观。

用法

ui-sref-active='class1 class2 class3'  // 当相关ui-sref的状态为活动时,将类“class1”,“class2”和“class3”分别添加到指令元素,并且当其不活动时将其删除。

例子

1
2
3
4
5
6
<ul>
<li ui-sref-active="active" class="item">
<a href ui-sref="app.user({user: 'zhangsan'})">@zhangsan</a>
</li>
<!-- ... -->
</ul>

当应用程序状态为“app.user”,并包含值为“zhangsan”的状态参数“user”时,生成的HTML将显示为

1
2
3
4
5
6
<ul>
<li ui-sref-active="active" class="item active">
<a ui-sref="app.user({user: 'zhangsan'})" href="/users/zhangsan">@zhangsan</a>
</li>
<!-- ... -->
</ul>

$state

$state.go()

$state.go可以在JavaScript中转换状态(类似ui-sref在html中转换)。

1
$state.go(to,params,options)

参数

  • to是stateName,必须,使用”^”或”.”表示相对状态;
  • params可空,类型是对象,挂载在对应$stateparams,参数也可以通过状态机制继承;
  • options可空,类型是对象,字段包括:
    • location:为boolean类型默认true,如果是true会更新地址中的url,flase不会,若为replace会更新并覆盖最后一此的记录。
    • inherit为boolean类型默认true,如果是true会继承最近的的url中的参数。
    • relative为对象默认$state.$current,当定义为相对路劲时,定义那个状态是相对的。
    • notify为boolean类型默认为true, 当为true时广播$stateChangeStart
      $stateChangeSuccess事件
    • reload为boolean类型默认为false,如果是true会强制重载且一切一样。

$state.reload()

强制重载当前状态,与$state.go的options内配制reload=true类似。

1
$state.reload('contact.detail'); //强制刷新contact.detail状态

$state.includes(stateName[, params])

$state.includes方法用于判断当前激活状态是否是指定的状态或者是指定状态的子状态,返回时boolean值.
比如当前状态是contacts.details.item

1
2
3
4
5
$state.includes("contacts"); // returns true
$state.includes("contacts.details"); // returns true
$state.includes("contacts.details.item"); // returns true
$state.includes("contacts.list"); // returns false
$state.includes("about"); // returns false

params,假设当前状态contacts.details.item.edit,其url是/contacts/1/address/edit,为:id参数填充1,:item参数填充address

1
2
3
$state.includes("contacts.detail", {id: 1}); // returns true
$state.includes("contacts.detail.item", {item:'address'}); // returns true
$state.includes("contacts", {bogus:'gnarly'}); // returns false

$state.is(stateOrName[, params])

类似$state.includes,但它比较严格。
假设当前状态:contact.details.item

1
2
3
$state.is("contact.details.item"); // returns true
$state.is(contactDetailItemStateConfigObj); // returns true
// 其他一切都会返回false

params:

1
2
$state.is("contacts.detail.item.edit", {id: 1, item: 'address'}); // returns true
// 其他一切都会返回false

$state.href(stateOrName [, params] [, options])

一个URL生成方法,返回已经填充指定参数的状态的编译后的链接。

参数

  • stateOeName:string,你想要生成的url的状态或者状态对象。
  • params:object,一个用于填充状态需要的参数的对象。
  • options:可选配置对象。
    • lossy(当第一个参数url未被提供时是否继承导航的url进行构建href)
    • inherit(是否继承当前url的参数)
    • relative(当变化相对路径:如”^,定义的状态是相对的)
    • absolute(是否生成绝对url)。
      1
      $state.href("about.person", { person: "bob" })

$state.get([stateName])

通过将名称作为字符串传递来检索任何状态的配置对象的方法

$state.current

返回状态对象的引用,用于访问自定义data,

关于$state在模板中使用

注意:使用$State和$stateparams需要将其挂载到$rootscope

1
2
3
4
angular.module("myApp").run(function ($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
});

现在,可以访问模板中的状态:

1
2
3
4
<ul class="nav">
<li ng-class="{ active: $state.includes('contacts') }"><a href="#/contacts">Contacts</a></li>
<li ng-class="{ active: $state.includes('about') }"><a href="#/about">About</a></li>
</ul>

$urlRouterProvider

UI-Router中,$urlRouterProvider负责监听$location.当$location变化的时候,$urlRouterProvider开始在一个规则的列表中一个个的查找,直到找到匹配的值。$urlRouterProvider用于在后端指定url的状态配置。所有的url被编译成UrlMatcher对象。

otherwise(rule)

定义一个当请求的路径是无效路径时跳转的路径,处理无效路由。

rule:你想重定向的url路径或一个返回的网址路径的规则函数。函数传入两个参数:$injector$location服务,而且必须返回一个string的url。

1
2
3
4
angular.module('Demo',['ui.router'])
.config(["$urlRouterProvider",function(){
$urlRouterProvider.otherwise(rule); // rule = 重定向的url
}])

rule(rule)

定义使用$urlRouterProvider 来匹配指定的URL的规则。

rule:将$injector$location作为arguments传入的处理函数。用来返回一个string类型的url路径。

1
2
3
4
5
6
7
8
9
10
angular.module('Demo',['ui.router'])
.config(["$urlRouterProvider",function($urlRouterProvider){
$urlRouterProvider.rule(function ($injector, $location) {
var path = $location.path(),
normalized = path.toLowerCase();
if (path !== normalized) {
return normalized;
}
});
}])

when(what,handler)

为给定的URL匹配注册一个处理程序。

参数:

  • what:需要重定向的传入路径,当前路径。
  • handler:需要重定向到的路径(或者是需要在路径被访问时运行的函数)

handler是路径:

1
2
3
4
5
6
7
app.config(function($urlRouterProvider){
// when there is an empty route, redirect to /index
$urlRouterProvider.when('', '/index');
// You can also use regex for the match parameter
$urlRouterProvider.when(/aspx/i, '/index');
})

handler是函数:

1
2
3
4
5
6
7
8
angular.module('Demo', ['ui.router']);
.config(["$urlRouterProvider",function ($urlRouterProvider) {
$urlRouterProvider.when($state.url, function ($match, $stateParams) {
if ($state.$current.navigable !== state || !equalForKeys($match, $stateParams) {
$state.transitionTo(state, $match, false);
}
});
}]);

如果提供一个函数处理,路由匹配的时候,这个函数就会被调用,它可以返回下列三种之一的结果。

  • false,这个回应告诉 $urlRouter 规则并不匹配,应该查找其它匹配的状态,在我们希望验证用户是否访问正确地址的时候很有用。
  • 字符串,$urlRouter 将其作为重定向目标。
  • true 或者 undefined,函数已经处理了这个 url

$stateProvider

$stateProvider用于配制’状态’,可以是链式配制。

1
2
$stateProvider.state(stateName1, stateConfig1)
.state(stateName2,stateConfig2);

state(stateName,stateConfig);

注册一个状态,并给定其配置。

参数

  • stateName是以个唯一的字符串,用于标记状态。
  • stateConfig用于管理状态的配制,object类型,配置具有以下各项属性:

    • template: html模板字符串,或者一个返回html模板字符串的函数。
    • templateUrl:模板路径的字符串,或者返回模板路径字符串的函数。
    • templateProvider:function,返回html模板字符串或模板路径的服务。
    • controller:string/function,新注册一个控制器函数或者一个已注册的控制器的名称字符串。
    • controllerProvider:function,返回控制器或者控制器名称的服务
    • controllerAs:string,控制器别名。
    • parent:string/object,手动指定该状态的父级。
    • resolve:object,将会被注入controller去执行的函数,形式。它为状态的控制器提供了所需的依赖.这些依赖可以给状态对应的控制器提供所需要的内容或数据.
    • url:string,当前状态的对应url。
    • views:object,视图展示的配置。形式。
    • abstract:boolean,一个永远不会被激活的抽象的状态,但可以给其子级提供特性的继承。默认是true。
    • onEnter:function,当进入一个状态后的回调函数。
    • onExit:function,当退出一个状态后的回调函数。
    • reloadOnSearch:boolean,如果为false,那么当一个search/query参数改变时不会触发相同的状态,用于当你修改$location.search()的时候不想重新加载页面。默认为true。

    • data:object,任意对象数据,用于自定义配置。继承父级状态的data属性。换句话说,通过原型继承可以达到添加一个data数据从而整个树结构都能获取到。

    • params:url里的参数值,通过它可以实现页面间的参数传递。

decorator(name,func);

通过内部的$stateProvider以扩展或者重写状态生成器。可用于添加ui-router的自定义功能,例如,基于状态名称推断templateUrl

警告:因为生成器的函数执行顺序的不确定,decorator不应该相互依赖。

参数:

  • name:需要修改的生成函数名称。
  • func:可选,负责修改生成器函数的函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$stateProvider.decorator('views', function (state, parent) {
var result = {},
views = parent(state);
angular.forEach(views, function (config, name) {
var autoName = (state.name + '.' + name).replace('.', '/');
config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
result[name] = config;
});
return result;
});
$stateProvider.state('home', {
views: {
'contact.list': { controller: 'ListController' },
'contact.item': { controller: 'ItemController' }
}
});
$state.go('home');

以上代码修饰了views直接通过state的名称绑定完对应的页面模板。

事件

状态更改事件

所有这些事件都从广播$rootScope。

  • $stateChangeSuccess - 一旦状态转换完成就触发。
  • $stateChangeStart - 当转换开始时触发。
  • $stateNotFound - 当无法通过其名称找到状态时触发。
  • $stateChangeError - 在转换期间发生错误时触发。

视图加载事件

  • $viewContentLoading - 当视图开始加载时(在渲染DOM之前)每个视图触发一次。广播从$rootScope。
  • $viewContentLoaded - 当视图加载时(在渲染DOM之后)每个视图触发一次。从视图发出$scope。

事件触发执行顺序

下面来理一下一个状态被激活的过程是怎样的:

  1. 触发$stateChangeStart事件,如果使用event.preventDefault(),会阻止状态改变. 如果没有找到对应状态,会触发$stateNotFound事件,然后中断.
  2. 触发$viewContentLoading事件.
  3. 如果在切换状态的过程中出错(比如resolve出错),触发$stateChangeError事件,无出错跳过此步.
  4. 触发上一个状态(若有)的onExit回调事件
  5. 触发当前状态的onEnter回调事件
  6. 触发$stateChangeSuccess事件
  7. 触发$viewContentLoaded事件

ui-router的单视图:

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
<div ng-app="Demo" ng-controller="testCtrl as ctrl">
<ol>
<li><a ui-sref="app">app</a></li>
<li><a ui-sref="test">test</a></li>
</ol>
<div ui-view></div>
</div>
<script type="text/ng-template" id="'page1.html'">
this is page 1 for app.
</script>
<script type="text/ng-template" id="'page3.html'">
this is page 1 for test.
</script>
angular.module('Demo', ['ui.router'])
.config(["$stateProvider","$urlRouterProvider",routeConfig])
.controller("testCtrl", angular.noop)
function routeConfig($stateProvider,$urlRouterProvider){
$urlRouterProvider.otherwise("/app");
$stateProvider
.state("app",{
url:"/app",
views:{
"":{
templateUrl:"'page1.html'"
}
}
})
.state("test",{
url:"/test",
views:{
"":{
templateUrl:"'page3.html'"
}
}
})
}

ui-router的t多视图:

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
<div ng-app="Demo" ng-controller="testCtrl as ctrl">
<ol>
<li><a ui-sref="app.page1">app</a></li>
<li><a ui-sref="test.page1({id:1})">test</a></li>
</ol>
<div ui-view></div>
</div>
<script type="text/ng-template" id="'layout.html'">
<div ui-view="nav@"></div>
<div ui-view></div>
</script>
<script type="text/ng-template" id="'nav1.html'">
<ol>
<li><a ui-sref="app.page1">app.page1</a></li>
<li><a ui-sref="app.page2">app.page2</a></li
</ol>
</script>
<script type="text/ng-template" id="'nav2.html'">
<ol>
<li><a ui-sref="test.page1({id:1})">test.page1</a></li>
<li><a ui-sref="test.page2">test.page2</a></li
</ol>
</script>
<script type="text/ng-template" id="'page1.html'">
this is page 1 for app.
</script>
<script type="text/ng-template" id="'page2.html'">
this is page 2 for app.
</script>
<script type="text/ng-template" id="'page3.html'">
this is page 1 for test.
</script>
<script type="text/ng-template" id="'page4.html'">
this is page 2 for test.
</script>
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
angular.module('Demo', ['ui.router'])
.config(["$stateProvider","$urlRouterProvider",routeConfig])
.controller("testCtrl", angular.noop)
function routeConfig($stateProvider,$urlRouterProvider){
$urlRouterProvider.otherwise("/app/page1");
$stateProvider
.state("app",{
url:"/app",
views:{
"":{
templateUrl:"'layout.html'"
},
"nav":{
templateUrl:"'nav1.html'"
}
}
})
.state("app.page1",{
url:"/page1",
templateUrl:"'page1.html'"
})
.state("app.page2",{
url:"/page2",
templateUrl:"'page2.html'"
})
.state("test",{
url:"/test",
views:{
"":{
templateUrl:"'layout.html'"
},
"nav":{
templateUrl:"'nav2.html'"
}
}
})
.state("test.page1",{
url:"/page1?:id",
templateUrl:"'page3.html'",
controller:["$stateParams",function($stateParams){
console.log($stateParams.id);// 1 这里实现传参
}],
params:{
id:null
}
})
.state("test.page2",{
url:"/page2",
templateUrl:"'page4.html'"
})
}

参考文档

https://github.com/angular-ui/ui-router/wiki/Quick-Reference