AngularJS進階(一)深入理解ANGULARUI路由_UI-ROUTER
深入理解ANGULARUI路由_UI-ROUTER
最近在用 ionic寫個webapp 看到幾個demo中路由有好幾種,搞的有點暈,查下資料研究下,做個筆記,其中大部分為摘抄別人的,做個說明免得被人吐槽。
Angularjs ui-router - 元件:
$state / $stateProvider:
管理狀態定義、當前狀態和狀態轉換。包含觸發狀態轉換的事件和回撥函式,非同步解決目標狀態的任何依賴項,更新$location到當前狀態。由於狀態包含關聯的 url,通過$urlRouterProvider生成一個路由規則來執行轉換的狀態。
ui-view指示器:
渲染狀態中定義的檢視,是狀態中定義的檢視的一個佔位符。
$urlRouter / $urlRouterProvider:
管理了一套路由規則列表來處理當$location發生變化時如何跳轉。最低階的方式是,規則可以是任意函式,來檢查$location,並在處理完成時候返回true。支援正則表示式規則和通過$urlMatcherFactory編譯的UrlMatcher物件的 url 佔位符規則。
$urlMatcherFactory:
將 url和佔位符編譯為UrlMatcher物件。除了$routeProvider支援的佔位符語法之外,它還支援擴充套件語法,允許一個正則表示式指定佔位符,並且能夠提取命名引數和查詢url的一部分。
$templateFactory :
通過$http / $templateCache來載入模板,供狀態配置中使用。
AngularJS不需要任何第三方庫,利用自身整合的各個模組便可開發出功能齊全的web應用,不過活躍的AngularJS社群也開發了很多能夠最大限度強化web應用的程式設計庫。本文帶讀者瞭解專業開發使用的模組AngularUI中的ui-路由(ui-router)。AngularUI庫已經被分成了幾個模組,使用者可以只選擇載入自己感興趣的模組,而不用載入整個庫。
UI-Router
UI-Router被認為是AngularUI為開發者提供的最實用的一個模組,它是一個讓開發者能夠根據URL狀態或者說是'機器狀態'來組織和控制介面
安裝
安裝方式可以選擇下載發行版本或者使用Bower(前端包管理器):
$ bower install angular-ui-router --save
同時也需要將原始檔包含到頁面中:
<script type="text/javascript" src="app/bower_components/angular-ui-router/release/angular-ui-router.js"></script>
接下來,將UI-Router作為web應用的依賴,注入到主程式:
angular.module('myApp', ['ui.router']);
與整合的ngRoute服務不同的是,UI-Router可以將檢視巢狀,因為它基於的是操作狀態而僅非URL。與傳統做法使用ng-view不同的是,在ngRoute裡需要使用ui-view服務。當在ui-router中處理路由和狀態時,開發者的重心是當前的狀態是什麼以及在哪一個頁面裡。
<div ng-controller="DemoController">
<div ui-view></div>
</div>
和ngRoute一樣,為特定狀態指定的模板將會放在<div ui-view></div>元素中。在這些模板中也可以包含自己的ui-view,這就是在同一個路由下實現巢狀檢視的方法。要定義一個路由,與傳統的方法相同:使用.config方式,但使用的不是$routeProvider而是$stateProvider。
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('start', {
url: '/start',
templateUrl: 'partials/start.html'
})
});
上述程式碼在設定物件上定義了一個叫start的狀態。設定物件stateConfig和路由設定物件的選項是非常相似的。
模板,模板路徑,模板Provider
開發者可以在每個檢視下使用如下方式來設定模板 - template - HTML字串,或者是返回HTML字串的函式 - templateUrl - HTML模板的路徑,或者是返回HTML模板路徑的函式 - templateProvider - 返回HTML字串的函式 例如:
$stateProvider.state('home', {
template: '<h1>Hello {{ name }}</h1>'
});
控制器
和ngRoute相似,開發者可以指定任何已經被註冊的控制器,或者在路由裡面建立一個作為控制器的函式。但如果沒有定義模板,控制器將無效。
預載入Resolve
使用預載入功能,開發者可以預先載入一系列依賴或者資料,然後注入到控制器中。在ngRoute中resolve選項可以允許開發者在路由到達前載入資料保證(promises)。在使用這個選項時比使用angular-route有更大的自由度。
預載入選項需要一個物件,這個物件的key即要注入到控制器的依賴,這個物件的value為需要被載入的factory服務。
如果傳入的是字串,angular-route會試圖匹配已經註冊的服務。如果傳入的是函式,該函式將會被注入,並且該函式返回的值便是控制器的依賴之一。如果該函式返回一個資料保證(promise),這個資料保證將在控制器被例項化前被預先載入並且資料會被注入到控制器中。
$stateProvider.state('home', {
resolve: {
//這個函式的值會被直接返回,因為它不是資料保證
person: function() {
return {
name: "Ari",
email: "[email protected]"
}
},
//這個函式為資料保證, 因此它將在控制器被例項化之前載入。
currentDetails: function($http) {
return $http({
method: 'JSONP',
url: '/current_details'
});
},
//前一個數據保證也可作為依賴注入到其他資料保證中!(這個非常實用)
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基於使用者瀏覽該應用所在的狀態。這樣當在瀏覽該應用的時候便能實現深度連結的效果。 該選項與ngRoute的URL相似,但可以被視為對ngRoute主要的升級,在接下來的文章裡你便會認可這一點。開發者可以這樣指定一個基本的路由。
$stateProvider
.state('inbox', {
url: '/inbox',
template: '<h1>Welcome to your inbox</h1>'
});
當用戶瀏覽到/inbox時,該應用將狀態改為inbox同時向主ui-view元素中插入模板中的內容('Welcome to your inbox')。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,那麼$stateParams.inboxId就為1($stateParams為{inboxId:1})。同時也可使用不同的語法:
url: '/inbox/{inboxId}'
路徑必須匹配URL,與ngRoute不同的是,當用戶訪問到/inbox/時,上面的的路徑會被啟用,然而當訪問到/inbox時不會被啟用。路徑同時也使開發者可以使用正則表示式來匹配,例如:
// 限定id為6位16進位制數字
url: '/inbox/{inboxId:[0-9a-fA-F]{6}}',
// 或者
// 匹配任何在 `/inbox`後面的url(慎用)並匹配值到indexId
url: '/inbox/{inboxId:.*}'
注意,在路由中目前還無法使用路由組,路由資料預載入器無法預載入。
在路徑裡可以指定查詢引數:
// /inbox?sort=ascending 將會被匹配
url: '/inbox?sort'
巢狀路由
使用url引數可以實現巢狀的路由,有了巢狀路由便可在同一個模板同一個路由實現多層次的ui-view,例如在/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下的一個子路由:state( . )語法指定了它使子路由。/inbox/1將匹配第一個路由,而/index/1/priority會匹配第二個路由。使用這種語法,在父檢視中的ui-view元素將會由第二個路由控制。
Params 路由引數
params選項是一個包含路徑中的引數和正則表示式匹配結果的陣列。該選項不能和url選項混用!當某狀態被啟用時,應用將這個陣列賦值給$stateParams服務。
Views 檢視
開發者可以在一個狀態中設定多個有名稱的檢視。該功能在ui-router中很強大,開發者可以在同一個模板中改變和切換不同的檢視。
<如果設定了檢視選項,則該狀態的‘template’,‘templateUrl’及‘templateProvider’將被忽略。如果想在路由裡包含父級模板,就需要建立一個包含模板的抽象模板。
例如有這樣的檢視:
<div>
<div ui-view="filters"></div>
<div ui-view="mailbox"></div>
<div ui-view="priority"></div>
</div>
接下來就可以建立將被分別被插入到上述ui-view的有命名的檢視了,每個子檢視可以包含自己的模板、控制器和預載入資料。
$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();
}
}
}
}
});
abstract 抽象模板
抽象模板不能被啟用,但是它的子模板可以被啟用。抽象模板可以提供一個包括了多個有名的檢視的模板,或者它可以傳遞作用域變數$scope給子模板。使用它可以在同一個url下傳遞自定義資料或者預載入的依賴。除了需要新增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>'
});
onEnter,onExit 回撥函式
當應用進入或者離開當前狀態的檢視時會呼叫這兩個函式。這兩個函式可以訪問預載入的資料。這兩個回撥函式使開發者可以根據狀態改變來採取某些動作,例如在使用者要離開時可以彈出對話方塊‘你確定嗎?’以及防止意外操作等。
Data 資料
自定義資料也可以被附加到狀態控制物件state configObject.該資料和預載入資料resolve屬性相似,但是該資料不會被注入到控制器中,promise也不會被預載入,它的用途是從父狀態傳遞資料到子狀態。
事件
和ngRoute相同的是,angular-route服務會在不同的狀態生命週期lifecycle裡啟動某些事件events。監聽$scope物件便可以捕獲這些事件然後採取不同的響應或者操作。如下的事件將會在$rootScope上觸發,因此在任何$scope物件上都可以監聽到這些事件。
狀態改變事件
$scope.$on('$stateChangeStart',
function(evt, toState, toParams, fromState, fromParams), {
// 如果需要阻止事件的完成
evt.preventDefault();
});
可以觸發的事件包括:
stateChangeStart
當狀態改變開始的時候被觸發
$stateChangeSuccess
當狀態改變成功後被觸發
$stateChangeError
當狀態改變遇到錯誤時被觸發,錯誤通常是目標無法載入,需要預載入的資料無法被載入等。
檢視載入事件
檢視載入階段ui-router也提供了一些事件
$viewContentLoading
當檢視正在被載入且在DOM被渲染之前觸發。
$scope.$on('$viewContentLoading',
function(event, viewConfig){
// 獲取任何檢視設定的引數,以及一個特殊的屬性:viewConfig.targetView
});
$viewContentLoaded
當檢視被載入且DOM已經渲染完成後被觸發。
$stateParams 狀態引數
在上面提及使用$stateparams來提取在url中的不同引數。該服務的作用是處理url的不同部分。例如,當上述的inbox狀態是這樣時:
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
和ngRoute一樣,開發者可以在該物件上設定特定的URL被啟用時做什麼的規則。由於設定好的狀態在特定的url被訪問是會自動啟用,所以$urlRouterProvider沒有必要用來管理啟用和載入狀態。但當需要管理哪些被髮生在當前狀態之外的作用域scope時它會非常有用,例如在重定向或者安全驗證的時候。在模組的設定函式裡便可使用$urlRouterProvider。
when()
該函式需要兩個引數:1.當前的路徑,2.需要重定向到的路徑(或者是需要在路徑被訪問是執行的函式)。設定重定向前需要為$urlRouterProvider設定when函式來接受一個字串。例如,當希望重定向一個空的路由到/inbox:
.config(function($urlRouterProvider) {
$urlRouterProvider.when('', '/inbox');
});
如果傳遞的是函式,在路徑被匹配時該函式會被執行,處理器返回如下3個值中的一個: - falsy,該回應告訴$urlRouter沒有匹配到當前url規則,應該嘗試匹配新的路徑,這樣能保證使用者訪問了正常的路徑。 - 字串,$urlRouter將該字串當做重定向的路徑。 - TRUE 或者 undefined,該回應告訴$urlRouter,url已被處理
otherwise()
和ngRoute的otherwise()函式相似,在使用者提交的路徑沒有被定義的時候它將重定向到指定的頁面。這是個建立’預設‘路徑的好方法。 otherwise()只接受一個引數,要麼函式要麼字串,字串必須為合法的url路由地址,函式則會在沒有任何路徑被匹配的時候被執行。
.config(function($urlRouterProvider) {
$urlRouterProvider.otherwise('/');
// or
$urlRouterProvider.otherwise(
function($injector, $location) {
$location.path('/');
});
});
rule()
如果想越過任何URL的匹配或者在其他路由前做路由修改,則可以使用rule()函式。在使用它的時候必須返回一個合法的代表路徑的字串。
app.config(function($urlRouterProvider){
$urlRouterProvider.rule(
function($injector, $location) {
return '/index';
});
})
總結
本文涵蓋了ui-router深度及幾乎全部的功能。希望你也發現這個庫的強大和實用,並在下一個專案中實用這些強大的功能。