安装
$ bower install angular-ui-router --save
<script type="text/javascript"
src="app/bower_components/angular-ui-router/release/angular-ui-router.js"></script>
angular.module('myApp', ['ui.router']);
现在,不同于内置的ngRoute
服务,由于ui-router
基于状态工作,而不是简单的url
,因此可以将它嵌套在视图中。
在处理ngRoute
服务时我们不再使用ng-view
,而改为使用ui-view
指令。
在ui-router
内处理路由和状态时,我们主要关心的是应用程序处在哪个状态以及Web应用当前处在哪个路由位置。
<div ng-controller="DemoController">
<div ui-view></div>
</div>
定义在任意给定状态内的模板都处在<div ui-view></div>
元素内。此外,每个模板都可以包含自己的ui-view
。 这事实上就允许你在路由中嵌套视图。定义路由:
.config(function($stateProvider,$urlRouterProvider) {
$stateProvider.state('start', {
url: '/start',
templateUrl: 'partials/start.html'
})
});
这一步给状态配置对象分配了一个名为start
的状态。这个状态配置对象的参数如下:
1.template、templateUrl、templateProvider
在每个视图上设置模板的方式有三种。
- template:一个HTML内容字符串或者返回HTML的函数
- templateUrl:一个模板URL路径字符串或者是返回URL路径字符串的函数
- templateProvider:一个返回HTML内容字符串的函数
2.controller
可以给已经注册好的控制器关联一个URL(使用字符串),也可以创建一个控制器函数作为状态控制器。如果没有定义模板,就不会创建这个控制器。
3.resolve
我们还可以使用resolve
功能解析要注入到控制器中的依赖列表。这个resolve
选项就是一个对象,其中键就是要注入到控制器中的依赖名称,而其值就是待解析的factories
。
如果传入一个字符串,angular-route
会尝试匹配一个现有的已注册的服务。如果传入一个函数,则注入这个函数,而函数的返回值就是依赖。如果这个函数返回一个promise
,它会在控制器被实例化之前解析,同时其值(就像ngRoute
)会注入到控制器中。
$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;
}
});
4.url
url
选项可以给应用程序的状态分配一个唯一的URL。这个url
选项提供了与深度链接同样的功能,它通过状态导航应用,而不是简单地通过URL导航。
基本路由可以像这样指定:
$stateProvider
.state('inbox', {
url: '/inbox',
template: '<h1>Welcome to your inbox</h1>'
});
当用户导航到/inbox
时,应用会转换到inbox
状态,然后使用模板内容填充主要的ui-view
指令。
URL可以接受一系列不同的选项,它还可以在url
中设置基本的参数。
$stateProvider
.state('inbox', {
url: '/inbox/:inboxId',
template: '<h1>Welcome to your inbox</h1>',
controller: function($scope, $stateParams) {
$scope.inboxId = $stateParams.inboxId;
}
});
应用会捕获作为URL第二个组成部分的:inboxId
。例如,如果用户转换到/inbox/1
,$stateParams.inboxId
就会变成1(因为$stateParams
为{inboxId: 1}
)。
还可以使用不同的语法:
url: '/inbox/{inboxId}'
这里路径必须与URL精确匹配。和ngRoute
不同,如果用户导航到/inbox/
,上面的路径能够正常工作。但是,当导航导到/inbox
时,上述示例配置中的状态不会被激活。
此外,你还可以在路径参数内使用正则表达式,因此你可以设置一个匹配路由的规则。
// 只匹配包含6个十六进制数字的inbox ID
url: '/inbox/{inboxId: [0-9a-fA-f]{6}}',
// 或者匹配每个URL中`/inbox`后面的`inboxId`(全部捕获)
url: '/inbox/{inboxId:.*} '
注意,不能在路由内使用正则捕获组,因为路由解析器将无法解析这个路由。
甚至还可以在路由中指定查询参数:
// 匹配诸如/inbox?sort=ascending形式的路由
url: '/inbox?sort'
5.嵌套路由
你可以使用url
参数以插入路由的方式提供嵌套路由。这让你可以在页面或者模板内有多个ui-views
。
$stateProvider.state('inbox', {
url: '/inbox/:inboxId',
template: '<div><h1>Welcome to your inbox</h1>\
<a ui-sref="inbox.priority">Show priority</a>\
<div ui-view></div></div>'
controller: function($scope, $stateParams) {
$scope.inboxId = $stateParams.inboxId;
}
})
.state('inbox.priority', {
url: '/priority',
template: '<h2>Your priority inbox</h2>'
});
第一个路由会按预期匹配。现在这里有了第二个路由,也就是一个匹配父路由inbox
之下的子路由。
-
/inbox/1
匹配第一个状态。 -
/inbox/1/priority
匹配第二个状态。
使用这种语法,你可以在父路由内嵌套URL。父视图中的ui-view
会解析priority
收件箱。
6.params
params
选项是一个参数名数组或者是一个正则表达式数组。不能将这个选项与url
选项联合使用。当状态被激活时,这些参数会被填充到$stateParams
服务中。
7.views
ui-router
的一个强大的特性就是可以在一个状态内设置多个命名视图。在独立的视图内,你可以在独立模板中定义多个要引用的视图。
如果设置了views
参数,那么状态的templateUrl
、template
和templateProvider
就会被忽略。如果你想在路由中包含父模板,就需要创建一个包含模板的抽象状态。
比方说我们有一个视图看起来像这样:
<div>
<div ui-view="filters"></div>
<div ui-view="mailbox"></div>
<div ui-view="priority"></div>
</div>
现在,你可以创建命名视图来填充每个独立的模板。每个子视图都可以包含它自己的模板、控制器和使用resolve
关键字解析的数据。
$stateProvider.state('inbox', {
views: {
'filters': {
template: '<h4>Filter inbox</h4>',
controller: function($scope) {}
},
'mailbox': {template: 'partials/mailbox.html'},
'priority': {
template: '<h4>Priority inbox</h4>',
resolve: {
facebook: function() {return FB.messages(); }
}
}
}
});
8.abstract
抽象模板永远不能直接激活,但是可以设置被激活的子节点。
你可以使用抽象模板提供一个模板包装器来包裹多个命名视图,或者传递$scope
对象给子节点。你还可以使用它们来传递解析后的依赖或者自定义数据,或者在同一url
下嵌套多个路由(比如,所有的路由都在/adminURL
之下)。
设置抽象模板与设置常规状态一样,区别只在于设置abstract
属性:
$stateProvider
.state('admin', {
abstract: true,
url: '/admin',
template: ;<div ui-view></div>'
})
.state('admin.index', {
url: '/index',
template: '<h3>Admin index</h3>'
})
.state('admin.users', {
url: '/users',
template: '<ul>...</ul>'
});
9.onEnter、onExit
Angular会在用户(分别)进入或者离开视图时调用这些回调函数。对于这两个选项,你可以设置希望调用的函数。这些函数可以访问被解析的数据。
这些回调函数让你可以在新视图上或者进入另一个状态时触发某个行为。使用它们可以很好地实现一个“你确定吗?”形式的模态视图,或者在用户进入这个状态之前要求用户登录。
10.data
你可以附加任意数据给你的状态配置对象configObject
。这个选项跟resolve
属性很像,但是它的数据不会被注入到控制器中,promise
也不会被解析。
当需要从父状态给子状态传递数据时,这个选项特别有用。
事件
angular-route
服务会在状态生命周期的不同阶段触发不同的事件。
在应用程序内可以通过监听$scope
对象的方式附加函数给这些事件。以下所有事件都会触发在$ootScope
上,因此可以在任意$scope
对象上监听这些事件。
1.状态改变事件
可以使用如下方式监听这个事件:
$scope.$on('$stateChangeStart',
function(evt, toState, roParams, fromState, fromParams) {
// 可以阻止这一状态完成
evt.preventDefault();
});
这个事件可能会以如下方式触发。
$stateChangeStart
从一个状态开始过渡到另一个状态时触发这个事件。 $stateChangeSuccess
从一个状态过渡到下一个状态完成时触发这个事件。 $stateChangeError
当过渡期间发生错误时触发这个事件。通常,模板不能被解析或者解析promise
失败时会引发错误。
2.视图加载事件
ui-router
还在视图加载阶段提供了事件。
$viewContentLoading
视图开始加载时,DOM被渲染之前,触发这个事件。
你可以像这样监听这个事件:
$scope.$on('$viewContentLoading',
function(event, viewConfig) {
// 在这里可以访问所有视图配置属性
// 以及一个特殊的“targetView”属性
// viewConfig.targetView
});
$viewContentLoaded
在视图加载完成以及DOM渲染之后触发这个事件。
$stateParams
$stateParams
服务展示了如何根据URL的不同组成部分处理数据。
例如,如果在inbox
状态中有个看起来像这样的URL:
url: 'inbox/:inboxId/messages/{sorted}}?from&to'
然后用户到达这个路由:
/inbox/123/messages/ascending?from=10&to=20
那么$stateParams
对象的结果就是:
{inboxId: '123', sorted: 'ascending', from: 10, to: 20}
$urlRouterProvider
你可以使用路由提供程序构建规则,规定当特定的URL被激活时会发生什么。 创建的这些状态负责在不同的URL中激活自身,因此不一定需要$urlRouterProvider
来管理激活和加载状态。当你想要管理发生在状态作用域之外的行为时,它就可以派上用场了,比如重定向或者身份验证。
你可以在模块配置函数中使用$urlRouterProvider
。
when()
when()
接受两个参数:想要匹配的入口路径和用于重定向的路径(或者是在路径匹配时调用的函数)。
为了设置重定向,需要给when
方法设置一个字符串参数。
例如,如果想将一个空路由重定向到/inbox:
.config(function($urlRouterProvider) {
$urlRouterProvider.when('', '/inbox');
});
如果传入一个函数,它会在路径匹配时调用。这个处理程序可能返回以下三个值中的一个。
- falsy:这个值告诉
$urlRouter
该规则不匹配,同时它应该尝试找到一个不同的状态来匹配。如果想要确保用户可以正确地访问一个URL,它将很有帮助。 - 字符串:
$urlRouter
会把这个字符串值当作重定向的URL。 - truthy or undefined:这个值让
$urlRouter
知道已经处理了URL。
otherwise()
otherwise()
方法在没有其他路由匹配时发起重定向。这个方法是创建默认URL的一种很好的方式。
otherwise()
方法接受一个参数:一个字符串或者函数。
如果传入一个字符串,任何无效或者不匹配的路由都会重定向到字符串指定的URL。
如果传入一个函数,它会在没有其他路由匹配时被调用,同时负责处理返回结果。
.config(function() {
$urlRouterProvider.otherwise('/');
// 或者
$urlRouterProvider.otherwise(function($injector, $location) {
$location.path('/');
});
});
rule()
如果想要绕过所有的URL匹配,或者想要在操作其他路由之前对路由做一些操作, 可以使用rule()
函数。
使用rule()
函数时必须返回一个有效路径字符串。
.config(function($urlRouterProvider) {
$rulRouterProvider.rule(function($injector, $location) {
return '/index';
});
});
创建一个导航程序
当我们想要为用户创建一个注册向导的时候,就需要使用ui-router
了,这是一个非常合适的应用场景。
我们将使用ui-router
创建一个快速注册服务,它包含一个控制器,用于处理注册任务。首先,需要创建应用视图:
<div ng-controller="WizardSignupController">
<h2>Signup wizard</h2>
<div ui-view></div>
</div>
接下来,在这个注册向导中还需要有三个阶段。
- start:在这个阶段,我们获取用户名并向其介绍注册向导。
- email:在这里,接受用户的邮件。
- finish:此时,用户完成注册过程,我们要向其展示一个完整的页面。
在真实的应用中,finish
阶段应该将注册资料发送给服务器,同时进行真实的注册 操作。在这里,由于没有后端,因此暂时只显示这个视图。
这个注册程序依赖于wizardapp.controllers
模块,我们将在其中编写包含控制器:WizardSignupController
。
angular.module('wizardApp', [
'ui.router',
'wizardapp.controllers'
]);
WizardSignupController
简单地提供了$scope.user
对象,在注册过程以及注册行为中,我 们都会使用这个对象。
angular.module('wizardapp.controllers', [])
.controller('WizardSignupController',
function($scope, $state) {
$scope.user = {};
$scope.signup = function() {}
});
向导程序逻辑覆盖了大部分工作。你可以将这些逻辑设置到应用的config()
函数中:
angular.module('wizardApp', [
'ui.router', 'wizardapp.controllers'
])
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('start', {
url: '/step_1',
/templateUrl: 'partials/wizard/step_1.html'
})
.state('email', {
url: '/step_2',
templateUrl: 'partials/wizard/step_2.html'
})
.state('finish', {
url: '/finish',
templateUrl: 'partials/wizard/step_3.html'
});
});
设置这些选项之后,基本流程就全部完成了。现在,如果用户导航到路由/step_1
,他们将被定向到流程的起点。尽管整个流程也可以都发生在根URL上(即/step_1
),但你可能更希望将 它们放在子路由中(如/wizard/step_1
)。
为此,只需要设置一个包装其他步骤的abstract
状态就可以了。
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('wizard', {
abstract: true,
url: '/wizard',
template: '<div><div ui-view></div></div>'
})
.state('wizard.start', {
url: '/step_1',
templateUrl: 'partials/wizard/step_1.html'
})
.state('wizard.email', {
url: '/step_2',
templateUrl: 'partials/wizard/step_2.html'
})
.state('wizard.finish', {
url: '/finish',
templateUrl: 'partials/wizard/step_3.html'
});
});
现在,这些路由不再定义在顶级路由中了,你可以将它们(子路由)安全地嵌套在/wizard
URL内。
此外,我们还想在注册程序的尾部附加一个功能:在父控制器WizardSignupController
上调用signup
函数。我们只需在向导程序的尾部设置一个控制器来调用$scope
上的函数就行了。 由于整个向导程序都封装在WizardSignupController
中,这就表示可以正常使用作用域嵌套作用域属性。
.state('wizard.finish',{
url: '/finish',
templateUrl: 'partials/wizard/step_3.html',
controller: function($scope) {
$scope.signup();
}