安裝
$ 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']);
現(xiàn)在,不同于內(nèi)置的ngRoute服務(wù),由于ui-router基于狀態(tài)工作,而不是簡單的url,因此可以將它嵌套在視圖中。
在處理ngRoute服務(wù)時(shí)我們不再使用ng-view,而改為使用ui-view指令。
在ui-router內(nèi)處理路由和狀態(tài)時(shí),我們主要關(guān)心的是應(yīng)用程序處在哪個(gè)狀態(tài)以及Web應(yīng)用當(dāng)前處在哪個(gè)路由位置。
<div ng-controller="DemoController">
<div ui-view></div>
</div>
定義在任意給定狀態(tài)內(nèi)的模板都處在<div ui-view></div>元素內(nèi)。此外,每個(gè)模板都可以包含自己的ui-view。 這事實(shí)上就允許你在路由中嵌套視圖。定義路由:
.config(function($stateProvider,$urlRouterProvider) {
$stateProvider.state('start', {
url: '/start',
templateUrl: 'partials/start.html'
})
});
這一步給狀態(tài)配置對象分配了一個(gè)名為start的狀態(tài)。這個(gè)狀態(tài)配置對象的參數(shù)如下:
1.template、templateUrl、templateProvider
在每個(gè)視圖上設(shè)置模板的方式有三種。
- template:一個(gè)HTML內(nèi)容字符串或者返回HTML的函數(shù)
- templateUrl:一個(gè)模板URL路徑字符串或者是返回URL路徑字符串的函數(shù)
- templateProvider:一個(gè)返回HTML內(nèi)容字符串的函數(shù)
2.controller
可以給已經(jīng)注冊好的控制器關(guān)聯(lián)一個(gè)URL(使用字符串),也可以創(chuàng)建一個(gè)控制器函數(shù)作為狀態(tài)控制器。如果沒有定義模板,就不會(huì)創(chuàng)建這個(gè)控制器。
3.resolve
我們還可以使用resolve功能解析要注入到控制器中的依賴列表。這個(gè)resolve選項(xiàng)就是一個(gè)對象,其中鍵就是要注入到控制器中的依賴名稱,而其值就是待解析的factories。
如果傳入一個(gè)字符串,angular-route會(huì)嘗試匹配一個(gè)現(xiàn)有的已注冊的服務(wù)。如果傳入一個(gè)函數(shù),則注入這個(gè)函數(shù),而函數(shù)的返回值就是依賴。如果這個(gè)函數(shù)返回一個(gè)promise,它會(huì)在控制器被實(shí)例化之前解析,同時(shí)其值(就像ngRoute)會(huì)注入到控制器中。
$stateProvider.state('home', {
resolve: {
// 當(dāng)結(jié)果不是promise時(shí)立即返回
person: function() {
return {
name: "Ari",
email: "ari@fullstack.io"
}
},
// 這個(gè)函數(shù)返回一個(gè)promise,它會(huì)在控制器實(shí)例化之前解析
currentDetails: function($http) {
return $http({
method: 'JSONP',
url: '/current_details'
});
},
// 還可以在另一個(gè)解析中使用上面返回的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選項(xiàng)可以給應(yīng)用程序的狀態(tài)分配一個(gè)唯一的URL。這個(gè)url選項(xiàng)提供了與深度鏈接同樣的功能,它通過狀態(tài)導(dǎo)航應(yīng)用,而不是簡單地通過URL導(dǎo)航。
基本路由可以像這樣指定:
$stateProvider
.state('inbox', {
url: '/inbox',
template: '<h1>Welcome to your inbox</h1>'
});
當(dāng)用戶導(dǎo)航到/inbox時(shí),應(yīng)用會(huì)轉(zhuǎn)換到inbox狀態(tài),然后使用模板內(nèi)容填充主要的ui-view指令。
URL可以接受一系列不同的選項(xiàng),它還可以在url中設(shè)置基本的參數(shù)。
$stateProvider
.state('inbox', {
url: '/inbox/:inboxId',
template: '<h1>Welcome to your inbox</h1>',
controller: function($scope, $stateParams) {
$scope.inboxId = $stateParams.inboxId;
}
});
應(yīng)用會(huì)捕獲作為URL第二個(gè)組成部分的:inboxId。例如,如果用戶轉(zhuǎn)換到/inbox/1,$stateParams.inboxId就會(huì)變成1(因?yàn)?code>$stateParams為{inboxId: 1})。
還可以使用不同的語法:
url: '/inbox/{inboxId}'
這里路徑必須與URL精確匹配。和ngRoute不同,如果用戶導(dǎo)航到/inbox/,上面的路徑能夠正常工作。但是,當(dāng)導(dǎo)航導(dǎo)到/inbox時(shí),上述示例配置中的狀態(tài)不會(huì)被激活。
此外,你還可以在路徑參數(shù)內(nèi)使用正則表達(dá)式,因此你可以設(shè)置一個(gè)匹配路由的規(guī)則。
// 只匹配包含6個(gè)十六進(jìn)制數(shù)字的inbox ID
url: '/inbox/{inboxId: [0-9a-fA-f]{6}}',
// 或者匹配每個(gè)URL中`/inbox`后面的`inboxId`(全部捕獲)
url: '/inbox/{inboxId:.*} '
注意,不能在路由內(nèi)使用正則捕獲組,因?yàn)槁酚山馕銎鲗o法解析這個(gè)路由。
甚至還可以在路由中指定查詢參數(shù):
// 匹配諸如/inbox?sort=ascending形式的路由
url: '/inbox?sort'
5.嵌套路由
你可以使用url參數(shù)以插入路由的方式提供嵌套路由。這讓你可以在頁面或者模板內(nèi)有多個(gè)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>'
});
第一個(gè)路由會(huì)按預(yù)期匹配。現(xiàn)在這里有了第二個(gè)路由,也就是一個(gè)匹配父路由inbox之下的子路由。
-
/inbox/1匹配第一個(gè)狀態(tài)。 -
/inbox/1/priority匹配第二個(gè)狀態(tài)。
使用這種語法,你可以在父路由內(nèi)嵌套URL。父視圖中的ui-view會(huì)解析priority收件箱。
6.params
params選項(xiàng)是一個(gè)參數(shù)名數(shù)組或者是一個(gè)正則表達(dá)式數(shù)組。不能將這個(gè)選項(xiàng)與url選項(xiàng)聯(lián)合使用。當(dāng)狀態(tài)被激活時(shí),這些參數(shù)會(huì)被填充到$stateParams服務(wù)中。
7.views
ui-router的一個(gè)強(qiáng)大的特性就是可以在一個(gè)狀態(tài)內(nèi)設(shè)置多個(gè)命名視圖。在獨(dú)立的視圖內(nèi),你可以在獨(dú)立模板中定義多個(gè)要引用的視圖。
如果設(shè)置了views參數(shù),那么狀態(tài)的templateUrl、template和templateProvider就會(huì)被忽略。如果你想在路由中包含父模板,就需要?jiǎng)?chuàng)建一個(gè)包含模板的抽象狀態(tài)。
比方說我們有一個(gè)視圖看起來像這樣:
<div>
<div ui-view="filters"></div>
<div ui-view="mailbox"></div>
<div ui-view="priority"></div>
</div>
現(xiàn)在,你可以創(chuàng)建命名視圖來填充每個(gè)獨(dú)立的模板。每個(gè)子視圖都可以包含它自己的模板、控制器和使用resolve關(guān)鍵字解析的數(shù)據(jù)。
$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
抽象模板永遠(yuǎn)不能直接激活,但是可以設(shè)置被激活的子節(jié)點(diǎn)。
你可以使用抽象模板提供一個(gè)模板包裝器來包裹多個(gè)命名視圖,或者傳遞$scope對象給子節(jié)點(diǎn)。你還可以使用它們來傳遞解析后的依賴或者自定義數(shù)據(jù),或者在同一url下嵌套多個(gè)路由(比如,所有的路由都在/adminURL之下)。
設(shè)置抽象模板與設(shè)置常規(guī)狀態(tài)一樣,區(qū)別只在于設(shè)置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會(huì)在用戶(分別)進(jìn)入或者離開視圖時(shí)調(diào)用這些回調(diào)函數(shù)。對于這兩個(gè)選項(xiàng),你可以設(shè)置希望調(diào)用的函數(shù)。這些函數(shù)可以訪問被解析的數(shù)據(jù)。
這些回調(diào)函數(shù)讓你可以在新視圖上或者進(jìn)入另一個(gè)狀態(tài)時(shí)觸發(fā)某個(gè)行為。使用它們可以很好地實(shí)現(xiàn)一個(gè)“你確定嗎?”形式的模態(tài)視圖,或者在用戶進(jìn)入這個(gè)狀態(tài)之前要求用戶登錄。
10.data
你可以附加任意數(shù)據(jù)給你的狀態(tài)配置對象configObject。這個(gè)選項(xiàng)跟resolve屬性很像,但是它的數(shù)據(jù)不會(huì)被注入到控制器中,promise也不會(huì)被解析。
當(dāng)需要從父狀態(tài)給子狀態(tài)傳遞數(shù)據(jù)時(shí),這個(gè)選項(xiàng)特別有用。
事件
angular-route服務(wù)會(huì)在狀態(tài)生命周期的不同階段觸發(fā)不同的事件。
在應(yīng)用程序內(nèi)可以通過監(jiān)聽$scope對象的方式附加函數(shù)給這些事件。以下所有事件都會(huì)觸發(fā)在$ootScope上,因此可以在任意$scope對象上監(jiān)聽這些事件。
1.狀態(tài)改變事件
可以使用如下方式監(jiān)聽這個(gè)事件:
$scope.$on('$stateChangeStart',
function(evt, toState, roParams, fromState, fromParams) {
// 可以阻止這一狀態(tài)完成
evt.preventDefault();
});
這個(gè)事件可能會(huì)以如下方式觸發(fā)。
$stateChangeStart從一個(gè)狀態(tài)開始過渡到另一個(gè)狀態(tài)時(shí)觸發(fā)這個(gè)事件。 $stateChangeSuccess從一個(gè)狀態(tài)過渡到下一個(gè)狀態(tài)完成時(shí)觸發(fā)這個(gè)事件。 $stateChangeError當(dāng)過渡期間發(fā)生錯(cuò)誤時(shí)觸發(fā)這個(gè)事件。通常,模板不能被解析或者解析promise失敗時(shí)會(huì)引發(fā)錯(cuò)誤。
2.視圖加載事件
ui-router還在視圖加載階段提供了事件。
$viewContentLoading視圖開始加載時(shí),DOM被渲染之前,觸發(fā)這個(gè)事件。
你可以像這樣監(jiān)聽這個(gè)事件:
$scope.$on('$viewContentLoading',
function(event, viewConfig) {
// 在這里可以訪問所有視圖配置屬性
// 以及一個(gè)特殊的“targetView”屬性
// viewConfig.targetView
});
$viewContentLoaded在視圖加載完成以及DOM渲染之后觸發(fā)這個(gè)事件。
$stateParams
$stateParams服務(wù)展示了如何根據(jù)URL的不同組成部分處理數(shù)據(jù)。
例如,如果在inbox狀態(tài)中有個(gè)看起來像這樣的URL:
url: 'inbox/:inboxId/messages/{sorted}}?from&to'
然后用戶到達(dá)這個(gè)路由:
/inbox/123/messages/ascending?from=10&to=20
那么$stateParams對象的結(jié)果就是:
{inboxId: '123', sorted: 'ascending', from: 10, to: 20}
$urlRouterProvider
你可以使用路由提供程序構(gòu)建規(guī)則,規(guī)定當(dāng)特定的URL被激活時(shí)會(huì)發(fā)生什么。 創(chuàng)建的這些狀態(tài)負(fù)責(zé)在不同的URL中激活自身,因此不一定需要$urlRouterProvider來管理激活和加載狀態(tài)。當(dāng)你想要管理發(fā)生在狀態(tài)作用域之外的行為時(shí),它就可以派上用場了,比如重定向或者身份驗(yàn)證。
你可以在模塊配置函數(shù)中使用$urlRouterProvider。
when()
when()接受兩個(gè)參數(shù):想要匹配的入口路徑和用于重定向的路徑(或者是在路徑匹配時(shí)調(diào)用的函數(shù))。
為了設(shè)置重定向,需要給when方法設(shè)置一個(gè)字符串參數(shù)。
例如,如果想將一個(gè)空路由重定向到/inbox:
.config(function($urlRouterProvider) {
$urlRouterProvider.when('', '/inbox');
});
如果傳入一個(gè)函數(shù),它會(huì)在路徑匹配時(shí)調(diào)用。這個(gè)處理程序可能返回以下三個(gè)值中的一個(gè)。
- falsy:這個(gè)值告訴
$urlRouter該規(guī)則不匹配,同時(shí)它應(yīng)該嘗試找到一個(gè)不同的狀態(tài)來匹配。如果想要確保用戶可以正確地訪問一個(gè)URL,它將很有幫助。 - 字符串:
$urlRouter會(huì)把這個(gè)字符串值當(dāng)作重定向的URL。 - truthy or undefined:這個(gè)值讓
$urlRouter知道已經(jīng)處理了URL。
otherwise()
otherwise()方法在沒有其他路由匹配時(shí)發(fā)起重定向。這個(gè)方法是創(chuàng)建默認(rèn)URL的一種很好的方式。
otherwise()方法接受一個(gè)參數(shù):一個(gè)字符串或者函數(shù)。
如果傳入一個(gè)字符串,任何無效或者不匹配的路由都會(huì)重定向到字符串指定的URL。
如果傳入一個(gè)函數(shù),它會(huì)在沒有其他路由匹配時(shí)被調(diào)用,同時(shí)負(fù)責(zé)處理返回結(jié)果。
.config(function() {
$urlRouterProvider.otherwise('/');
// 或者
$urlRouterProvider.otherwise(function($injector, $location) {
$location.path('/');
});
});
rule()
如果想要繞過所有的URL匹配,或者想要在操作其他路由之前對路由做一些操作, 可以使用rule()函數(shù)。
使用rule()函數(shù)時(shí)必須返回一個(gè)有效路徑字符串。
.config(function($urlRouterProvider) {
$rulRouterProvider.rule(function($injector, $location) {
return '/index';
});
});
創(chuàng)建一個(gè)導(dǎo)航程序
當(dāng)我們想要為用戶創(chuàng)建一個(gè)注冊向?qū)У臅r(shí)候,就需要使用ui-router了,這是一個(gè)非常合適的應(yīng)用場景。
我們將使用ui-router創(chuàng)建一個(gè)快速注冊服務(wù),它包含一個(gè)控制器,用于處理注冊任務(wù)。首先,需要?jiǎng)?chuàng)建應(yīng)用視圖:
<div ng-controller="WizardSignupController">
<h2>Signup wizard</h2>
<div ui-view></div>
</div>
接下來,在這個(gè)注冊向?qū)е羞€需要有三個(gè)階段。
- start:在這個(gè)階段,我們獲取用戶名并向其介紹注冊向?qū)А?/li>
- email:在這里,接受用戶的郵件。
- finish:此時(shí),用戶完成注冊過程,我們要向其展示一個(gè)完整的頁面。
在真實(shí)的應(yīng)用中,finish階段應(yīng)該將注冊資料發(fā)送給服務(wù)器,同時(shí)進(jìn)行真實(shí)的注冊 操作。在這里,由于沒有后端,因此暫時(shí)只顯示這個(gè)視圖。
這個(gè)注冊程序依賴于wizardapp.controllers模塊,我們將在其中編寫包含控制器:WizardSignupController。
angular.module('wizardApp', [
'ui.router',
'wizardapp.controllers'
]);
WizardSignupController簡單地提供了$scope.user對象,在注冊過程以及注冊行為中,我 們都會(huì)使用這個(gè)對象。
angular.module('wizardapp.controllers', [])
.controller('WizardSignupController',
function($scope, $state) {
$scope.user = {};
$scope.signup = function() {}
});
向?qū)С绦蜻壿嫺采w了大部分工作。你可以將這些邏輯設(shè)置到應(yīng)用的config()函數(shù)中:
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'
});
});
設(shè)置這些選項(xiàng)之后,基本流程就全部完成了。現(xiàn)在,如果用戶導(dǎo)航到路由/step_1,他們將被定向到流程的起點(diǎn)。盡管整個(gè)流程也可以都發(fā)生在根URL上(即/step_1),但你可能更希望將 它們放在子路由中(如/wizard/step_1)。
為此,只需要設(shè)置一個(gè)包裝其他步驟的abstract狀態(tài)就可以了。
.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'
});
});
現(xiàn)在,這些路由不再定義在頂級路由中了,你可以將它們(子路由)安全地嵌套在/wizardURL內(nèi)。
此外,我們還想在注冊程序的尾部附加一個(gè)功能:在父控制器WizardSignupController上調(diào)用signup函數(shù)。我們只需在向?qū)С绦虻奈膊吭O(shè)置一個(gè)控制器來調(diào)用$scope上的函數(shù)就行了。 由于整個(gè)向?qū)С绦蚨挤庋b在WizardSignupController中,這就表示可以正常使用作用域嵌套作用域?qū)傩浴?/p>
.state('wizard.finish',{
url: '/finish',
templateUrl: 'partials/wizard/step_3.html',
controller: function($scope) {
$scope.signup();
}