原文地址:http://www.ng-newsletter.com/posts/angular-ui-router.htmljavascript
ui-router: https://angular-ui.github.io/ui-router/site/#/api/ui.router html
ui-router 是 AngularUI 库提供的特别有用的一个部分,是一个经过提供状态机机制,而不是简单的 URL 来组织咱们的界面的路由框架。java
这个库提供了针对视图的众多的额外控制,咱们能够建立嵌套的视图,在单个页面使用多个视图,多个视图来控制单个视图,还有更多特性。对于更加精细的控制和更为复杂的应用,ui-router 是很是棒的工具。git
ui-router 从状态着手来管理路由,它将应用视为多个状态的组合,经过状态的切换进行路由。github
安装 ui-router,既能够直接下载发布版本,也能够经过 bower 来获取。正则表达式
$ bower install angular-ui-router --save
而后,须要在页面中进入这个库,固然要先加载 angular 库。api
<script type="text/javascript" src="angular-ui-router.js"></script>
在 angular 中注入 ui.router.数组
angular.module('myApp', ['ui.router'])
不像 angular 内置的 ngRoute, ui-router 能够嵌套视图,它是基于状态,而不 URL 的。promise
也不像 ngRoute 使用 ng-view 指令,在 ui-router 中,咱们使用 ui-view 指令。安全
当在 ui-router 中考虑路由和状态的关系时,咱们主要关注应用的什么状态对应应用的什么路由。
<div ng-controller="DemoController"> <div ui-view></div> </div>
相似 ngRoute, 对于给定的状态,模板中的内容将会填充到 <div ui-view></div> 元素,每一个模板还能够包含本身的 ui-view ,这就是咱们能够支持嵌套路径的缘由。
定义路径的时候,咱们使用 .config 方法,像一般同样,可是使用 $stateProvider 来替换 $routeProvider。
.config(function($stateProvider, $urlRouterProvider) { $stateProvider .state('start', { url: '/start', templateUrl: 'partials/start.html' }) });
这样,咱们;定义了名为 start 的状态,传递的对象定义了状态配置信息,或者称为 stateConfig,相似于路由配置对象,咱们经过它配置状态信息。
可使用下面三种之一的方式来定义视图使用的模板:
例如
$stateProvider.state('home', {
template: '<h1>Hello {{ name }}</h1>' });
相似于 ngRoute,咱们既能够经过控制器的名字来关联一个预约义的控制器,也能够直接建立一个控制器函数来处理。
若是没有对应的模板定义,控制器对象就不会被建立。
使用 resolve 功能,咱们能够准备一组用来注入到控制器中的依赖对象。在 ngRoute 中,resolve 能够在路由实际渲染以前解决掉 promise
resolve 选项提供一个对象,对象中的 key 就是准备注入 controller 的依赖名称,值则是建立对象的工厂。
若是是一个串,就试图用这个串来匹配当前已经注册的服务名称,若是是一个函数,执行这个函数,返回的值就是依赖。若是函数返回一个 promise,在控制器被实例化以前,将会被 resolved,返回的值被注入到 controller 中。
$stateProvider.state('home', {
resolve: {
// This will return immediately as the // result is not a promise person: function() { return { name: "Ari", email: "ari@fullstack.io" } }, // This function returns a promise, therefore // it will be resolved before the controller // is instantiated currentDetails: function($http) { return $http({ method: 'JSONP', url: '/current_details' }); }, // We can use the resulting promise in another // resolution 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; } })
url 用来设置应用对应的一个特定状态. 也就是说,咱们能够经过 url 来到达某个特定的状态,因此这里的 url 不是简单的 url 地址,而是某个可到达状态的标志。
这个特性相似于 ngRoute 中的 URL, 可是,能够被看做一个重大的升级,后面咱们就会看到。
简单的路由相似下面所示。
$stateProvider
.state('inbox', { url: '/inbox', template: '<h1>Welcome to your inbox</h1>' });
当咱们导航到 /index 的时候,应用将会过渡到 inbox 状态,使用这里提供的内容模板填充 ui-view 的内容。
URL 中能够包含多种内容,使人难以置信的强大,能够像在 ngRoute 中设置简单的参数。
$stateProvider
.state('inbox', { url: '/inbox/:inboxId', template: '<h1>Welcome to your inbox</h1>', controller: function($scope, $stateParams) { $scope.inboxId = $stateParams.inboxId; } });
这里,咱们建立了 :inboxId 参数来捕获 url 中的第二部分,例如,若是应用访问 /inbox/1,那么,$stateParameter.inboxId 就成为 1, 实际上, $stateParams 的值将为 { inboxId: 1 }
也可使用另一种语法。
url: '/inbox/{inboxId}'
路径必须彻底匹配,不像 ngRoute, 若是用户访问 /inbox/,这个路径配置将会工做,可是,若是访问 /inbox,这个状态就不会被激活。
还可使用正则表达式来表示参数,这样能够经过正则表达式来设置匹配规则,例如。
// Match only inbox ids that contain // 6 hexidecimal digits url: '/inbox/{inboxId:[0-9a-fA-F]{6}}', // Or // match every url at the end of `/inbox` // to `inboxId` (a catch-all) url: '/inbox/{inboxId:.*}'
注意,不能在路由中使用捕获组
甚至能够在路径中使用查询参数。
// will match a route such as // /inbox?sort=ascending url: '/inbox?sort'
若是你使用绝对 url 方式,须要在 url 字符串的开发加上特殊字符 ^
$stateProvider
.state('contacts', { url: '/contacts', ... }) .state('contacts.list', { url: '^/list', ... });
'contacts'
状态将匹配"/contacts"
'contacts.list'
状态将匹配"/list"
。子状态的url没有附在父状态的url以后的,由于使用了^
。
咱们可使用 url 参数添加到路由中来实现嵌套路由。这样能够提供多个 ui-views 在咱们的页面中,例如,咱们能够在 /inbox 之上,提供嵌套的独立路由。这里使用了子状态。
$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。
params 选项是参数名称或者正则的数组。它不能合并 url 选项,当状态激活的时候,应用会使用这些参数填充 $stateParams 服务。
咱们能够在 state 中提供命名的视图。这是强大的特性,在单个视图中,咱们能够定义多个视图,甚至使用单个模板。
若是咱们使用了 views 参数,那么,templateUrl, template 和 templateProvider 就会忽略。若是咱们但愿包含父模板,咱们须要建立一个抽象模板。
假如咱们有以下模板。
<div> <div ui-view="filters"></div> <div ui-view="mailbox"></div> <div ui-view="priority"></div> </div>
主要注意的是,顶级的状态天然对应母版页中的视图,通常这个视图是 noname 的,因此,须要一个 noname 的视图来匹配这个 placeholder。其它的视图须要匹配父状态中的视图 placeholder,这些 placeholder 能够是命名的,也能够是 naname的,天然,noname 的只能有一个,不然没法进行区分,咱们在 provider 中进行配置的时候,就须要描述清楚这些 view 和 placeholder 之间的对应关系。
使用 @ 能够定义绝对命名的视图名称,@ 的前面是 placeholder 的名称,后面是状态的名称。@ 前面为空表示未命名的 ui-view,@ 后面为空表示相对于根模板,一般是 index.html
咱们能够建立命名的视图,而后填充对应的模板。每一个子视图均可以有特有的模板,控制器和数据。
$stateProvider
.state('inbox', { views: { 'filters': { template: '<h4>Filter inbox</h4>', controller: function($scope) {} }, 'mailbox': { templateUrl: 'partials/mailbox.html' }, 'priority': { template: '<h4>Priority inbox</h4>', resolve: { facebook: function() { return FB.messages(); } } } } });
在这个例子中,咱们有两个命名的视图嵌套在抽象视图中。
咱们永远不能直接激活抽象模板,可是,能够经过派生模板来激活。
抽象模板提供封装命名视图的模板,能够传递 $scope 对象给派生子模板。能够经过它解决依赖问题,或者特定数据处理,或者简单地一样的 url 来嵌套多个路由,例如,全部路由都在 /admin 下面。
$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>' });
在应用进入或者退出视图的时候,会调用这些回调函数。它们均可以设置回调函数;函数能够访问获取的数据。
这些回调函数能够提供咱们一些能力,在访问新视图,或者改变当前状态的时候。这里是很好的执行 "Are you sure?" 对话框,或者请求用户在进入以前登录的地方。
两个函数都不提供参数,须要的信息须要本身提供。
咱们能够附加任意的数到咱们的状态配置对象 configObject 上,data 属性相似于 resolve 属性,除了不会注入到控制器,也不会 resolve promise。
当须要从父状态向子状态传递数据的时候,附加数据是方便的途径。
相似 ngRoute 服务,angular-route 服务在状态生命周期的不一样时间点会触发多种事件。咱们能够经过在 $scope 中监听来处理这些事件。
全部的下面的事件都会在 $rootScope 中触发,因此,咱们能够在任何 $scope 对象中监听这些事件。
能够以下监听
$scope.$on('$stateChangeStart',
function(evt, toState, toParams, fromState, fromParams), { // We can prevent this state from completing evt.preventDefault(); });
当从一个状态开始向另一个状态过分的时候触发。
当状态过渡完成以后触发。
在状态过渡中出现错误。常见的错误例如,不能获取模板,或者 promise 不能成功 resolve 等等
ui-router 也提供了视图加载阶段的事件。
视图开始加载,可是,在 DOM 渲染以前。
能够以下监听。
$scope.$on('$viewContentLoading',
function(event, viewConfig){ // Access to all the view config properties. // and one special property 'targetView' // viewConfig.targetView });
视图已经加载,渲染完成。
在前面的内容中,咱们使用 $stateParams 来从 url 参数中获取 params ,这个服务,与 url 不一样。
例如,若是咱们 inbox 状态的 url 以下。
url: '/inbox/:inboxId/messages/{sorted}?from&to'
用户使用下面的 url 访问
/inbox/123/messages/ascending?from=10&to=20
咱们的 $stateParams 对象将获取以下数据。
{inboxId: '123', sorted: 'ascending', from: 10, to: 20}
相似 ngRoute, 能够建立当特定的 url 访问时处理的规则。
能够经过不一样的 url 激活不一样的状态,因此在管理激活和加载状态的时候, $urlRouterProvider 并非必须的。在状态管理以外的时候才会须要,好比重定向,或者验证的时候。
when 函数须要两个参数,咱们但愿匹配的路径,另外就是咱们但愿从新定向的目标。也能够是一个函数。
例如,若是但愿任何空的路由到咱们的 /inbox 路由中。
.config(function($urlRouterProvider) { $urlRouterProvider.when('', '/inbox'); });
若是提供一个函数处理,路由匹配的时候,这个函数就会被调用,它能够返回下列三种之一的结果。
与 ngRoute 中的 oterwise() 方法相似,oterwiese() 在没有其它路由匹配的状况下重定向。这是建立默认 url 的好方法。
otherwise() 函数只须要一个参数,一个字符串或者一个函数。
若是提供了一个字符串,就会被看作一个默认地址,在任何错误的或者不能匹配任何路由的时候,就会被重定向到这个地址。
若是是一个函数,在没有其它路由匹配的时候,就会被执行
.config(function($urlRouterProvider) { $urlRouterProvider.otherwise('/'); // or $urlRouterProvider.otherwise( function($injector, $location) { $location.path('/'); }); });
若是咱们但愿处理任何路由,或者在其它路由以前进行一些处理,可使用 rule() 函数。
咱们必须返回一个验证的路径串
app.config(function($urlRouterProvider){ $urlRouterProvider.rule( function($injector, $location) { return '/index'; }); })
有三种方式来激活特定的状态
为何不使用一下它呢?
咱们建立一个报名的向导来演练一下 ui-router 的使用。
使用 ui-router ,咱们建立一个简单的报名服务,使用一个控制器来处理报名。
首先,咱们建立应用的视图。
<div ng-controller="WizardSignupController"> <h2>Signup wizard</h2> <div ui-view></div> </div>
在这个视图中,咱们定义了报名视图。下一步,在报名向导中,须要三步
报名处理依赖 wizardapp.controllers 模块,
angular.module('wizardApp', [
'ui.router', 'wizardapp.controllers' ]);
咱们的 wizardSignupController 控制器,使用 $scope.user 对象在整个过程当中收集信息。
angular.module('wizardapp.controllers', [])
.controller('WizardSignupController', ['$scope', '$state', function($scope, $state) { $scope.user = {}; $scope.signup = function() {} }]);
如今,向导处理逻辑处理主要工做,配置 config()
angular.module('wizardApp', [
'ui.router', 'wizardapp.controllers' ]) .config(['$stateProvider', '$urlRouterProvider', 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,就会看到开始页面,尽管如今地址是 /step_1, 而咱们但愿是 /wizard/step_1
为了这个效果,咱们建立 abstract 状态来寄宿各个步骤。
.config(['$stateProvider', '$urlRouterProvider',
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 之下了。
在状态之间进行导航,咱们使用 ui-router 提供的指令 ui-sref 来生成连接,这个指令用来生成导航连接。
例如,step_1.html 以下。
<!-- step_1.html --> <h3>Step 1</h3> <form ng-submit=""> <input type="text" ng-model="user.name" placeholder="Your name" /> <input type="submit" class="button" value="Next" ui-sref="wizard.email"/> </form>
咱们还但愿在报名流程完成以后,执行特定的动做,来调用定义在父控制器上的 signup 函数,咱们能够在最后的步骤中添加一个控制器来调用 $scope.signup() 函数。因为整个向导封装在 WizardSignupControoler 中,咱们能够像一般同样访问嵌套的 scope 对象。
.state('wizard.finish', {
url: '/finish', templateUrl: 'partials/wizard/step_3.html', controller: function($scope) { $scope.signup(); } });
在这里,咱们深刻讨论了 ui-router 几乎所有的特性,咱们发现这个库很是有用,但愿也能帮到你。