以下是今天演示的需求:
- 設計三個State, home、heroPanel、heroDetail,
home為首頁,heroPanel顯示英雄(hero)的 id 和 name 的列表,點列表的某一個 hero 會進到heroDetail,顯示 Detail 頁面。 - 頁面結構分成三個區塊,header、body和footer,三者的內容皆會跟據State而有所改變。
成品就像下面影片這樣:
首先先來看一下設計的檔案結構:
index.html為主要頁面,且此例也只有一個頁面,並在同一頁中利用UI-Router切換內容。
因為這邊我用npm的方式下載安裝AngularJS和UI-Router,所以有package.json,AngularJS和UI-Router都裝在node_modules裡,當然自己去官網下載也OK。
app資料夾下的為主要JS程式,第一層以app開頭的JS為主要AngularJS Module及其相關設訂,代表著 "home" 的狀態。
其他的資料夾, heroPanel 和 heroDetail 代表 heroPanel 和 heroDetail 兩個狀態,為ui-view是body時的內容設定。
common 資料夾下放著跟 footer 和 header 兩個 ui-view相關的設定
main.css 為 header、 body、footer 標上外框以利識別,也為 class="active" 的元素標上底色以利識別現在狀態。
接下來來看各檔案裡的程式內容
main.css :
.header, .footer, .body{
border : 1px solid red;
}
.active{
background-color: #58ff93;
}
index.html :
<!DOCTYPE html>
<html ng-app="app">
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/main.css">
<script src="node_modules/angular/angular.js"></script>
<script src="node_modules/angular-ui-router/release/angular-ui-router.js"></script>
<!-- app -->
<script src="app/app.js"></script>
<script src="app/app.service.js"></script>
<script src="app/app.run.js"></script>
<script src="app/app.route.js"></script>
<!-- header -->
<script src="app/common/header/header.module.js"></script>
<script src="app/common/header/header.controller.js"></script>
<!-- footer -->
<script src="app/common/footer/footer.module.js"></script>
<script src="app/common/footer/footer.controller.js"></script>
<!-- heroPanel -->
<script src="app/heroPanel/heroPanel.module.js"></script>
<script src="app/heroPanel/heroPanel.route.js"></script>
<script src="app/heroPanel/heroPanel.controller.js"></script>
<!-- heroDetail -->
<script src="app/heroDetail/heroDetail.module.js"></script>
<script src="app/heroDetail/heroDetail.route.js"></script>
<script src="app/heroDetail/heroDetail.controller.js"></script>
</head>
<body>
<div ui-view="header" class="header"></div>
<div ui-view="body" class="body" autoscroll></div>
<div ui-view="footer" class="footer"></div>
</body>
</html>
以下是跟 app 有關JS的程式碼:
app.js :
(function(){
angular.module("app", ["ui.router", "app.header", "app.footer", "app.heroPanel", "app.heroDetail"]);
})();
設定了 app module,並將 ui-router 的module和 header, footer, heroPanel, heroDetail module引入。app.route.js :
(function() {
angular.module("app").config(config);
config.$inject = [ '$stateProvider' ];
function config($stateProvider) {
$stateProvider.state('home', {
url : '',
params: {
},
data: {
},
views : {
'body@' : {
template : "這是主頁"
},
'header@' : {
templateUrl : '/app/common/header/header.html',
controller : 'headerController',
controllerAs : 'ctrl'
},
'footer@' : {
templateUrl : '/app/common/footer/footer.html',
controller : 'footerController',
controllerAs : 'ctrl'
}
}
});
}
})();
利用$stateProvider設定了State為home時的route設定,包括了 body, header, footer 三個 ui-view 使用的templateUrl, controller, controllerAs。
(function() {
angular.module('app').run(run);
run.$inject = [ '$transitions' , 'stateService'];
function run($transitions, stateService) {
//detect any "to" stage success
$transitions.onEnter({to: true }, function($transition){
console.log($transition.to().name);
stateService.setState($transition.to().name);
console.log(stateService.getState());
});
}
})();
AngularJS的run()可在執行controller之前執行,在此時例用$transitions來監聽State轉換事件(新版的 ui-router 用法),利用自製的stateService (請看app.service.js) 設定儲存State狀態。app.service.js :
(function(){
angular.module("app").factory("stateService", function(){
var state = "none state";
return {
setState : function(newState){
state = newState;
},
getState : function(){
return state;
}
};
});
angular.module("app").factory("heroService", function(){
var heroList = [{
id : 1,
name : "Hero1",
superPower : "SuperPower1"
},{
id : 2,
name : "Hero2",
superPower : "SuperPower2"
},{
id : 3,
name : "Hero3",
superPower : "SuperPower3"
},{
id : 4,
name : "Hero4",
superPower : "SuperPower4"
},{
id : 5,
name : "Hero5",
superPower : "SuperPower5"
}];
return {
getHeroList : function(){
return heroList;
},
getHeroById : function(id){
var hero;
var i;
for (i = 0 ; i < heroList.length; i++){
console.log(typeof id);
console.log(typeof heroList[i].id);
console.log(id === heroList[i].id);
if (id === heroList[i].id){
hero = heroList[i];
break;
}
}
return hero;
}
};
});
})();
設計stateService用來取得或設定目前State狀態,heroService只是提供hero 列表資訊的Servic。再來看比較簡單的 footer 和 header,因為內容基本一樣,所以只介紹 header, footer可以以此類推。
header.module.js:
(function() {
angular.module("app.header", []);
})();
只是一個module宣告。header.controller.js :
(function () {
angular.module('app.header').controller('headerController',
[
"stateService",
function headerController(stateService) {
var self = this;
self.stateString = stateService.getState();
}
]
);
/* 以下為另一種寫法
headerController.$inject = ["stateService"];
function headerController(stateService) {
var self = this;
self.stateString = stateService.getState();
}
*/
})();
header的controller設定,引進stateService來獲取現在State並設定在自己scope中的變數上。header.html :
<a ui-sref="home" ui-sref-active="active">主頁</a>
<a ui-sref="heroPanel" ui-sref-active="active">Hero Panel</a>
<b ng-class="{'active' : ctrl.stateString == 'heroDetail'}">Hero Detail</b>
這是Header,現在狀態為 : {{ctrl.stateString}}
放上前往State為home及heroPanel的連結、"主頁"、"Hero Panel"、"Hero detail"會在當下為其State時標上active的class,並且用文字顯示現在的stateString。接著來看 State 為 heroPanel 的程式碼:
heroPanel.module.js :
(function() {
angular.module("app.heroPanel", []);
})();
module的宣告heroPanel.route.js :
(function() {
angular.module("app.heroPanel").config(config);
config.$inject = [ '$stateProvider' ];
function config($stateProvider) {
$stateProvider.state('heroPanel', {
url : '/heroPanel',
params: {
},
data: {
},
views : {
'body@' : {
templateUrl : '/app/heroPanel/heroPanel.html',
controller : 'heroPanelController',
controllerAs : 'ctrl'
},
'header@' : {
templateUrl : '/app/common/header/header.html',
controller : 'headerController',
controllerAs : 'ctrl'
},
'footer@' : {
templateUrl : '/app/common/footer/footer.html',
controller : 'footerController',
controllerAs : 'ctrl'
}
}
});
}
})();
heroPanel的route設定,設定了header, body, footer要使用的templateUrl, controller, controllerAs。heroPanel.controller.js :
(function() {
angular.module('app.heroPanel').controller('heroPanelController', heroPanelController);
heroPanelController.$inject = ["stateService", "heroService"];
function heroPanelController(stateService, heroService) {
var self = this;
self.stateString = stateService.getState();
self.heroList = heroService.getHeroList();
}
})();
heroPanel的controller設定,由stateService和heroService取得目前State和 hero 列表。heroPanel.html:
<div>我是{{ctrl.stateString}}</div>
<div ng-repeat="hero in ctrl.heroList">
<a ui-sref="heroDetail({heroId : hero.id})">{{hero.id}} : {{hero.name}}</a>
</div>
heroPanel狀態時body的ui-view內容,顯示 hero 列表,並為每個 hero 加上 ui-sref 連結,連到heroDetail狀態,並帶上一個 heroId 參數。最後是 heroDetail 相關程式碼。
heroDetail.module.js :
(function() {
angular.module("app.heroDetail", []);
})();
heroDetail狀態時的module宣告。heroDetail.route.js :
(function() {
angular.module("app.heroDetail").config(config);
config.$inject = [ '$stateProvider' ];
function config($stateProvider) {
$stateProvider.state('heroDetail', {
url : '/heroDetail/{heroId:int}', //{hero : int } is wrong, can't have space
views : {
'body@' : {
templateUrl : '/app/heroDetail/heroDetail.html',
controller : 'heroDetailController',
controllerAs : 'ctrl'
},
'header@' : {
templateUrl : '/app/common/header/header.html',
controller : 'headerController',
controllerAs : 'ctrl'
},
'footer@' : {
templateUrl : '/app/common/footer/footer.html',
controller : 'footerController',
controllerAs : 'ctrl'
}
},
resolve : {
hero : ["$stateParams", "heroService", function($stateParams, heroService){
return heroService.getHeroById($stateParams.heroId);
}]
}
});
}
})();
heroDetail狀態的route宣告,指定了ui-view要使用的 templateUrl, controller, controllerAs,並且注意在 url 設定中指定了會有一個 int 的 heroId 參數輸入,其中 {heroId:int} 不可寫成中間有空格的 {heroId : int},否則會出錯 (原因不明)。resolve 可以為 heroDetail 指定的 controller 設定可用的 service,在這裡設定了一個叫做 hero 的 service,並用 heroService(heroId) 回傳 hero的detail資訊,可以注意到使用了$stateParams來讀取 url 傳進的 heroId 參數。
heroDetail.controller.js :
(function() {
angular.module('app.heroDetail').controller('heroDetailController', heroDetailController);
heroDetailController.$inject = ["stateService", "hero"];
function heroDetailController(stateService, hero) {
var self = this;
self.stateString = stateService.getState();
self.hero = hero;
}
})();
heroDetail的controller設定,設定了目前的stateString和 hero 詳細資訊。heroDetail.html :
<div>我是{{ctrl.stateString}}</div>
<div>id : {{ctrl.hero.id}}</div>
<div>name : {{ctrl.hero.name}}</div>
<div>superpower : {{ctrl.hero.superPower}}</div>
顯示stateString 和 hero 的詳細資訊。原始碼下載:
angularJS_ui-router.7z
參考資料:
- UI-Router (官網)
- UI-Router (Github)
- 初談 ui-router
- $stateChangeStart not being fired on v1.0 #2720 (提到新版UI-Router監測State變化的用法)

沒有留言 :
張貼留言