UI as route

ui-routerangular-ui 旗下一員大將,其 github README 介紹的主要特色如下:

  1. 使用 state 取代 angular 原生的 route
  2. 可巢狀的 state<ui-view> (view template directive)
  3. 支援多個 <ui-view>,可以給定名稱作為 reference 依據

特別點出來的原因在於今天要講的都不是這些,而是我打從一開始看到 ui-router 就想要使用它的真正原因

既不是巢狀 view ,也不是它的 state,而是 state 衍生出的 UI as route 能力。

所以是?

改變 URL 之後,能夠在不變動(也不重載) view 的內容的情況下吃到新的參數而產生不一樣的 state

用講的很抽象,這邊來看一個例子。

建議使用 popout 模式觀看,比較能夠看出網址列的變化。

從例子中可以發現這是應該是一個製作 webapp 常常會使用到的功能,接下來就來看看如何利用 ui-router 實作出來。

ui-router 實作

.config 設定

首先在 .config 內的 configuration 內容一般狀態可能如下:

$stateProvider
  .state('home', {
    url: '/home',
    templateUrl: 'home.html',
    controller: 'HomeCtrl'
  });

這時候需要利用 ui-router 的巢狀特性,讓子 state 改變的時候不會去 reload 當前(母 state)的 <ui-view> 內容。

由於 home.child 只是做為我們更新 $stateParams 的手段,它根本不需要 <ui-view> directive,這邊就不將 template / templateUrlcontroller 選項加入它的 state configuration 中(controller 在沒有 template 的情況下還是會 initalize,不過由於已經沒有 on-screen 的 attach 目標,這邊索性不加)。

$stateProvider
  .state('home', {
    url: '/home',
    templateUrl: 'home.html',
    controller: 'HomeCtrl'
  })
    .state('home.child', {
      url: '/:pos'
    });

感知變動並更新 view

一般標準的 $stateChangeSuccess 之後會觸發 controller 去 attach 新的 scope 到目標的 <ui-view> 上,因此我們會把每次 state change 之後執行的內容寫在綁定的 controller 裡面;然而在上面的設定之下,我們已經沒有會跟著 initialize 的 controller,因此需要一些寫法來幫助我們執行在 UI as route 的 state change 之後需要進行的動作。

直接綁定 $stateParams

可以選擇綁在任一上級 $scope 或是 $rootScope 上,綁定的 view model 名稱可以隨意更改(這邊用的是 $stateParams),以不要與 view 裡面有用到的混淆為準則。

/**
 * 綁在 $rootScope
 */
.run(function ($rootScope, $stateParams) {
  $rootScope.$stateParams = $stateParams;
})

/**
 * 綁在任一上級 $scope
 */
.controller('MainCtrl', function ($scope, $stateParams) {
  $scope.$stateParams = $stateParams;
})

在 view 裡面使用(假定 pos 輸入為數字):

<div class="index-number" ng-class="['a', 'b', 'c', 'd'][$stateParams.pos]">
  目前的 pos: {{ $stateParams.pos }}
</div>

使用 $scope.$on

$scope.$on 的好處是可以擺在特定想要應對的位置,就結構上來講比較沒有用到 scope inheritance,或許對整個 app 的架構有正面的幫助。

.controller('SomeCtrl', function ($scope, $stateParams) {
  $scope.$on('$stateChangeSuccess', function () {
    $scope.params = angular.copy($stateParams);
  });
})

在 view 裡面使用(假定 pos 輸入為數字):

<div class="index-number" ng-class="['a', 'b', 'c', 'd'][params.pos]">
  目前的 pos: {{ params.pos }}
</div>

使用 $scope.$watch

$scope.$watch 的用法類似 $scope.$on

.controller('SomeCtrl', function ($scope, $stateParams) {
  $scope.$watch(function (scope) {
    return $stateParams;
  }, function (newParams, oldParams) {
    $scope.params = newParams;
  });
});

在 view 裡面使用(假定 pos 輸入為數字):

<div class="index-number" ng-class="['a', 'b', 'c', 'd'][params.pos]">
  目前的 pos: {{ params.pos }}
</div>

不同的 UI as route

最後,做為觸類旁通的這個範例展示兩種不同的 UI as route 作法:

  • 使用 state 的 parameter - #/?theater=1
  • 使用 state - #/theater

一樣建議使用 popout 模式觀看。

後記

ui-router 這 module 真的讓 angular app 有了更多的可能性,也期待它繼續發展下去。

之後也會有更多關於 ui-router 的文章喔!