【JS-6】
如何使用ui-router?
小課堂【武漢分院第137期】
分享人:徐恒
目錄
1.背景介紹
2.知識剖析
3.常見問題
4.解決方案
5.編碼實戰
6.擴展思考
7.參考文獻
8.更多討論
一.背景介紹
angular路由
路由(route),幾乎所有的MVC(VM)框架都應該具有的特性,因為它是前端構建單頁面應用(SPA)必不可少的組成部分。
那么,對于angular而言,它自然也有內置的路由模塊:叫做ngRoute。
不過,大家很少用它,因為它的功能太有限,往往不能滿足開發需求!!
于是,一個基于ngRoute開發的第三方路由模塊,叫做ui.router,受到了大家的“追捧”。
ngRoute vs ui.router
首先,無論是使用哪種路由,作為框架額外的附加功能,它們都將以模塊依賴的形式被引入,簡而言之就是:在引入路由源文件之后,你的代碼應該這樣寫(以ui.router為例):
angular.module("myApp", ["ui.router"]); // myApp為自定義模塊,依賴第三方路由模塊ui.router
這樣做的目的是:在程序啟動(bootstrap)的時候,加載依賴模塊(如:ui.router),將所有掛載在該模塊的服務(provider),指令(directive),過濾器(filter)等都進行注冊,那么在后面的程序中便可以調用了。
說到這里,就得看看ngRoute模塊和ui.router模塊各自都提供了哪些服務,哪些指令?
ngRoute
$routeProvider(服務提供者) ——— 對應于下面的urlRouterProvider和stateProvider
$route(服務) ——— 對應于下面的urlRouter和state
$routeParams(服務) ——— 對應于下面的stateParams
ng-view(指令) ——— 對應于下面的ui-view
ui.router
$urlRouterProvider(服務提供者) ——— 用來配置路由重定向
$urlRouter(服務)
$stateProvider(服務提供者) ——— 用來配置路由
$state(服務) ——— 用來顯示當前路由狀態信息,以及一些路由方法(如:跳轉)
$stateParams(服務) ——— 用來存儲路由匹配時的參數
ui-view(指令) ——— 路由模板渲染,對應的dom相關聯
ui-sref(指令)
(注:服務提供者:用來提供服務實例和配置服務。)
這樣一看,其實ui.router和ngRoute大體的設計思路,對應的模塊劃分都是一致的(畢竟是同一個團隊開發),不同的地方在于功能點的實現和增強。
那么問題來了:ngRoute弱在哪些方面,ui.router怎么彌補了這些方面?
這里,列舉兩個最重要的方面來說(其他細節,后面再說):
多視圖
嵌套視圖
多視圖
多視圖:頁面可以顯示多個動態變化的不同區塊。
這樣的業務場景是有的:
比如:頁面一個區塊用來顯示頁面狀態,另一個區塊用來顯示頁面主內容,當路由切換時,頁面狀態跟著變化,對應的頁面主內容也跟著變化。
首先,我們嘗試著用ngRoute來做:
html
區塊1
區塊2
js$routeProvider? ? .when('/', {? ? ? ? template: 'hello world'? ? });
我們在html中利用ng-view指令定義了兩個區塊,于是兩個div中顯示了相同的內容,這很合乎情理,但卻不是我們想要的,但是又不能為力,因為,在ngRoute中:
視圖沒有名字進行唯一標志,所以它們被同等的處理。
路由配置只有一個模板,無法配置多個。
ok,針對上述兩個問題,我們嘗試用ui.router來做:
html
js$stateProvider? ? .state('home', {? ? ? ? url: '/',? ? ? ? views: {? ? ? ? ? ? '': {? ? ? ? ? ? ? ? template: 'hello world'? ? ? ? ? ? },? ? ? ? ? ? 'status': {? ? ? ? ? ? ? ? template: 'home page'? ? ? ? ? ? }? ? ? ? }? ? });
這次,結果是我們想要的,兩個區塊,分別顯示了不同的內容,原因在于,在ui.router中:
可以給視圖命名,如:ui-view=”status”。
可以在路由配置中根據視圖名字(如:status),配置不同的模板(其實還有controller等)。
注:視圖名是一個字符串,不可以包含@(原因后面會說)。
嵌套視圖
嵌套視圖:頁面某個動態變化區塊中,嵌套著另一個可以動態變化的區塊。
這樣的業務場景也是有的:
比如:頁面一個主區塊顯示主內容,主內容中的部分內容要求根據路由變化而變化,這時就需要另一個動態變化的區塊嵌套在主區塊中。
其實,嵌套視圖,在html中的最終表現就像這樣:
I am parent
I am child
轉成javascript,我們會在程序里這樣寫:$routeProvider? ? .when('/', {? ? ? ? template: 'I am parent
I am child
'? ? });
倘若,你真的用ngRoute這樣寫,你會發現瀏覽器崩潰了,因為在ng-view指令link的過程中,代碼會無限遞歸下去。
那么造成這種現象的最根本原因:路由沒有明確的父子層級關系!
看看ui.router是如何解決這一問題的?
$stateProvider? ? .state('parent', {? ? ? ? abstract: true,? ? ? ? url: '/',? ? ? ? template: 'I am parent
'? ? })? ? .state('parent.child', {? ? ? ? url: '',? ? ? ? template: 'I am child'? ? });
巧妙地,通過parent與parent.child來確定路由的父子關系,從而解決無限遞歸問題。
另外子路由的模板最終也將被插入到父路由模板的div[ui-view]中去,從而達到視圖嵌套的效果。
記住下面是ui.router用到的所有指令
ui.router
$urlRouterProvider(服務提供者) ——— 用來配置路由重定向
$urlRouter(服務)
$stateProvider(服務提供者) ——— 用來配置路由
$state(服務) ——— 用來顯示當前路由狀態信息,以及一些路由方法(如:跳轉)
$stateParams(服務) ——— 用來存儲路由匹配時的參數
ui-view(指令) ——— 路由模板渲染,對應的dom相關聯
ui-sref(指令)
初級應用(會用就行)
介紹用這個例子($urlRouter和$stateParams沒用到,其他都用到:$urlRouterProvider,$stateProvider,$state,ui-view,ui-sref)
var photoGallery = angular.module('photoGallery',["ui.router"]);
photoGallery.config(function($stateProvider, $urlRouterProvider){
$urlRouterProvider.otherwise('/home');
$stateProvider
.state('home',{
url: '/home',
templateUrl: 'partials/home.html'
})
.state('photos',{
url: '/photos',
templateUrl: 'partials/photos.html'
})
.state('about',{
url: '/about',
templateUrl: 'partials/about.html'
})
})
首先給大家介紹angular-ui-router的基本用法。
如何引用依賴angular-ui-router就是
再從簡單的開始(其實這幾個服務也都是有依賴的,例如$urlRouterProvider依賴$urlMatcherFactoryProvider? $locationProvider。$urlRouterProvider依賴$urlRouterProvider $urlMatcherFactoryProvider。但初級應用這不展開,只講要用到某幾個方法(不是全部))
$urlRouteProvider(總共3個when,otherwise和rule)這里懂when和otherwise就行。
$urlRouteProvider.when(whenPath, toPath)
為給定的URL匹配注冊一個處理程序。
$urlRouterProvider.otherwise(path)
定義一個當請求的路徑是無效路徑時跳轉的路徑。
例子:就用到一種情況(當請求的路徑是無效路徑時跳轉的路徑)
$urlRouterProvider
.otherwise('/login');
// .when("","/login");
$stateProvider(總共2個方法:decorator和state。但這里只講state(state這個里面的配置也不講全的,只講幾個暫時用到的))
處理路由狀態的服務,路由的狀態反映了該項在應用程序中的位置,描述了在當前狀態下UI是應該怎么樣的,并且該做什么。
方法:
state(name,stateConfig);
注冊一個狀態,并給定其配置。
//state可以有子父級
$stateProvider.state("home",{});
$stateProvider.state("home.child",{})
//state可以是鏈式的
$stateProvider.state("home",{}).state("about",{}).state("photos",{});
參數:
name:狀態的名稱。
stateConfig:狀態配置對象。配置具有以下各項屬性(全部屬性,看一下好了):
template: string/function,html模板字符串,或者一個返回html模板字符串的函數。
templateUrl:string/function,模板路徑的字符串,或者返回模板路徑字符串的函數。
templateProvider:function,返回html模板字符串或模板路徑的服務。
controller:string/function,新注冊一個控制器函數或者一個已注冊的控制器的名稱字符串。
controllerProvider:function,返回控制器或者控制器名稱的服務
controllerAs:string,控制器別名。
parent:string/object,手動指定該狀態的父級。
resolve:object,將會被注入controller去執行的函數,string,function>形式。
url:string,當前狀態的對應url。
views:object,視圖展示的配置。string,object>形式。
abstract:boolean,一個永遠不會被激活的抽象的狀態,但可以給其子級提供特性的繼承。默認是true。
onEnter:function,當進入一個狀態后的回調函數。
onExit:function,當退出一個狀態后的回調函數。
reloadOnSearch:boolean,如果為false,那么當一個search/query參數改變時不會觸發相同的狀態,用于當你修改$location.search()的時候不想重新加載頁面。默認為true。
data:object,任意對象數據,用于自定義配置。繼承父級狀態的data屬性。換句話說,通過原型繼承可以達到添加一個data數據從而整個樹結構都能獲取到。
params:url里的參數值,通過它可以實現頁面間的參數傳遞。
這里用到了
url:string,當前狀態的對應url。
params:url里的參數值,通過它可以實現頁面間的參數傳遞。
template: string/function,html模板字符串,或者一個返回html模板字符串的函數。
templateUrl:string/function,模板路徑的字符串,或者返回模板路徑字符串的函數。
resolve:object,將會被注入controller去執行的函數,string,function形式。
例子:
$stateProvider
.state("login",{
url: "/login",
templateUrl: "tpls/login.html",
resolve:{
load:['$ocLazyLoad',function($ocLazyLoad){
return $ocLazyLoad.load([
'css/login.css',
'js/login.js'
]);
}]
}
})
進一步
.state("main.article-list",{
url: "/article-list/:page/:size/:startAt/:endAt/:type/:status",
params:{'page':"1",'size':"10"},
templateUrl: "tpls/article-list.html",
resolve:{
load:['$ocLazyLoad',function($ocLazyLoad){
return $ocLazyLoad.load([
'css/article-list.css',
'js/article-list.js'
]);
}]
}
})
再說下
$state(總共6個方法和1個事件:go,href,include,is,reload,transitionTo和事件:$stateChangeError,$stateChangeStart,$stateChangeSuccess和$stateNotFound。這里只要會go就行)
$state服務負責代表狀態及提供狀態之間的轉換。它還提供你當前的狀態及上一個狀態。
方法:
go(to,params,options);
參數:
to:string,即將跳轉的狀態。
params:object,跳轉所帶的參數。
options:object,可選配置對象。有 location(是否更新地址欄的url,或以什么字符串替換url),inherit(是否繼承當前url的參數),relative(當變化相對路徑:如"^,定義的狀態是相對的),notify(是否廣播$stateChangeStart和$stateChangeSuccess事件),reload(是否重新載入)。
實例:
$state.go("login");
跳轉到在前面中定義的狀態那里
$stateProvider
.state("login",{})
然后就是^和.的用法區別,一個是向上一級,一個向下一級
$state.go('photos.detail')
$state.go('^')到上一級,比如從photo.detail到photo
$state.go('^.list')到相鄰state,比如從photo.detail到photo.list
$state.go('^.detail.comment')到孫子級state,比如從photo.detail到photo.detial.comment
ui-sref(2種,一種不帶參數,一種帶)
一種將鏈接(a標簽)綁定到一個狀態的指令。點擊該鏈接將觸發一個可以帶有可選參數的狀態轉換。
ui-sref="stateName"ui-sref="stateName({param:value,param:value})"代碼:首頁你的主頁
ui-view
一種是沒有名字的
一種是有名字的
來一個demo1
項目文件結構
node_modules/ //這里放各種依賴
partials/? ? //這里放跳的頁面
.....about.html
.....home.html
.....photos.html
app.js? ? ? //寫router
index.html
index.html文件? 就是一個導航欄(注意這里有ui-sref的跳轉)+一個ui-view(當然是跳轉后的各個頁面的東西)+依賴
app.js? 寫的ui.router
var photoGallery = angular.module('photoGallery',["ui.router"]);
photoGallery.config(function($stateProvider, $urlRouterProvider){
$urlRouterProvider.otherwise('/home');
$stateProvider
.state('home',{
url: '/home',
templateUrl: 'partials/home.html'
})
.state('photos',{
url: '/photos',
templateUrl: 'partials/photos.html'
})
.state('about',{
url: '/about',
templateUrl: 'partials/about.html'
})
})
更進一步就是task6-10(低版本)
router.js
var myApp = angular.module("myApp",['ui.router','oc.lazyLoad','ngMessages','tm.pagination']);
myApp.config(function ($stateProvider,$urlRouterProvider) {
$urlRouterProvider
.otherwise('/login');
// .when("","/login");
$stateProvider
.state("main.article-list",{
url: "/article-list/:page/:size/:startAt/:endAt/:type/:status",
params:{'page':"1",'size':"10"},
templateUrl: "tpls/article-list.html",
resolve:{
load:['$ocLazyLoad',function($ocLazyLoad){
return $ocLazyLoad.load([
'css/article-list.css',
'js/article-list.js'
]);
}]
}
})
});
上面用到了url的參數,params的參數,以及resolve。當然后面會用到$stateParams。
url中的/:page/:size/在params中初始化,然后傳參數過去。resolve中的是懶加載。
上面URL傳過來的參數在article-list.js中用到(分頁插件用的),通過$stateParams獲取。
article-list.js
var listApp = angular.module("myApp",[]);
listApp.controller("listCtrl",['$scope','$http','$state','$stateParams',function ($scope,$http,$state,$stateParams) {
// 分頁部分
$scope.paginationConf = {
showFlag:0,
// 當前頁
currentPage: 1,
// 每頁默認
itemsPerPage: 10,
// 點擊每個分頁按鈕都會觸發這個函數,然后刷新加載
onChange:function () {
console.log('$scope.paginationConf.currentPage=' );
console.log($scope.paginationConf.currentPage );
console.log('$scope.paginationConf.itemsPerPage=');
console.log($scope.paginationConf.itemsPerPage);
$state.go('main.article-list', {
page: $scope.paginationConf.currentPage ,
size: $scope.paginationConf.itemsPerPage
}, {reload: true});
}};
console.log('$stateParams=' );
console.log($stateParams );
$scope.paginationConf.currentPage = $stateParams.page ;
$scope.paginationConf.itemsPerPage = $stateParams.size ;
二.知識剖析
三.常見問題
四.解決方案
五.代碼實戰
六.拓展思考
七.參考文獻
八.更多討論
鳴謝
感謝大家觀看
BY : 徐恒
1.ngRoute能做到多視圖嵌套么
2.ng-view能命名么
3.ui-router這么多參數常用的是那些?
4.還有哪些情況下是適合ngRoute使用的
------------------------------------------------------------------------------------------------------------------------
技能樹.IT修真院
“我們相信人人都可以成為一個工程師,現在開始,找個師兄,帶你入門,掌控自己學習的節奏,學習的路上不再迷茫”。
這里是技能樹.IT修真院,成千上萬的師兄在這里找到了自己的學習路線,學習透明化,成長可見化,師兄1對1免費指導。快來與我一起學習吧 !