以下是今天演示的需求:
- 設計三個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變化的用法)
沒有留言 :
張貼留言