轉:深刻 AngularUI Router

原文地址:http://www.ng-newsletter.com/posts/angular-ui-router.htmljavascript

ui-router: https://angular-ui.github.io/ui-router/site/#/api/ui.router html

ui-router 是 AngularUI 庫提供的特別有用的一個部分,是一個經過提供狀態機機制,而不是簡單的 URL 來組織咱們的界面的路由框架。java

這個庫提供了針對視圖的衆多的額外控制,咱們能夠建立嵌套的視圖,在單個頁面使用多個視圖,多個視圖來控制單個視圖,還有更多特性。對於更加精細的控制和更爲複雜的應用,ui-router 是很是棒的工具。git

ui-router 從狀態着手來管理路由,它將應用視爲多個狀態的組合,經過狀態的切換進行路由。github

  • 一個狀態能夠對應一個頁面地址,經過特定的地址到達應用的特定狀態。
  • 經過狀態的 controller、template 和 views 來管理特定狀態的 UI 和行爲
  • 經過嵌套視圖來解決頁面中重複出現的內容。

 

Installation

安裝 ui-router,既能夠直接下載發佈版本,也能夠經過 bower 來獲取。正則表達式

$ bower install angular-ui-router --save

而後,須要在頁面中進入這個庫,固然要先加載 angular 庫api

<script type="text/javascript" src="angular-ui-router.js"></script>

在 angular 中注入 ui.router.數組

angular.module('myApp', ['ui.router'])

不像 angular 內置的 ngRoute, ui-router 能夠嵌套視圖,它是基於狀態,而不 URL 的。promise

也不像 ngRoute 使用 ng-view 指令,在 ui-router 中,咱們使用 ui-view 指令。安全

當在 ui-router 中考慮路由和狀態的關係時,咱們主要關注應用的什麼狀態對應應用的什麼路由。

<div ng-controller="DemoController">
  <div ui-view></div>
</div>

相似 ngRoute, 對於給定的狀態,模板中的內容將會填充到 <div ui-view></div> 元素,每一個模板還能夠包含本身的 ui-view ,這就是咱們能夠支持嵌套路徑的緣由。

定義路徑的時候,咱們使用 .config 方法,像一般同樣,可是使用 $stateProvider 來替換 $routeProvider。

.config(function($stateProvider, $urlRouterProvider) {
  $stateProvider
    .state('start', {
      url: '/start',
      templateUrl: 'partials/start.html'
    })
});

這樣,咱們;定義了名爲 start 的狀態,傳遞的對象定義了狀態配置信息,或者稱爲 stateConfig,相似於路由配置對象,咱們經過它配置狀態信息。

template, templateUrl, templateProvider

可使用下面三種之一的方式來定義視圖使用的模板:

  • template, 字符串方式的模板內容,或者是一個返回 HTML 的函數
  • templateUrl, 模板的路徑,或者返回模板路徑的函數
  • templateProvider, 返回 HTML 內容的函數

例如

$stateProvider.state('home', {
  template: '<h1>Hello {{ name }}</h1>'
});

Controller

相似於 ngRoute,咱們既能夠經過控制器的名字來關聯一個預約義的控制器,也能夠直接建立一個控制器函數來處理。

若是沒有對應的模板定義,控制器對象就不會被建立。

Resolve

使用 resolve 功能,咱們能夠準備一組用來注入到控制器中的依賴對象。在 ngRoute 中,resolve 能夠在路由實際渲染以前解決掉 promise

resolve 選項提供一個對象,對象中的 key 就是準備注入 controller 的依賴名稱,值則是建立對象的工廠。

若是是一個串,就試圖用這個串來匹配當前已經註冊的服務名稱,若是是一個函數,執行這個函數,返回的值就是依賴。若是函數返回一個 promise,在控制器被實例化以前,將會被 resolved,返回的值被注入到 controller 中。

$stateProvider.state('home', {
  resolve: {
    // This will return immediately as the 
    // result is not a promise
    person: function() {
      return {
        name: "Ari",
        email: "ari@fullstack.io"
      }
    },
    // This function returns a promise, therefore
    // it will be resolved before the controller
    // is instantiated
    currentDetails: function($http) {
      return $http({
        method: 'JSONP',
        url: '/current_details'
      });
    },
    // We can use the resulting promise in another
    // resolution
    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 來到達某個特定的狀態,因此這裏的 url 不是簡單的 url 地址,而是某個可到達狀態的標誌。

這個特性相似於 ngRoute 中的 URL, 可是,能夠被看做一個重大的升級,後面咱們就會看到。

簡單的路由相似下面所示。

$stateProvider
  .state('inbox', {
    url: '/inbox',
    template: '<h1>Welcome to your inbox</h1>'
  });

當咱們導航到 /index 的時候,應用將會過渡到 inbox 狀態,使用這裏提供的內容模板填充 ui-view 的內容。

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,那麼,$stateParameter.inboxId 就成爲 1, 實際上, $stateParams 的值將爲 { inboxId: 1 }

也可使用另一種語法。

url: '/inbox/{inboxId}'

路徑必須徹底匹配,不像 ngRoute, 若是用戶訪問 /inbox/,這個路徑配置將會工做,可是,若是訪問 /inbox,這個狀態就不會被激活。

還可使用正則表達式來表示參數,這樣能夠經過正則表達式來設置匹配規則,例如。

// Match only inbox ids that contain
// 6 hexidecimal digits
url: '/inbox/{inboxId:[0-9a-fA-F]{6}}',
// Or
// match every url at the end of `/inbox`
// to `inboxId` (a catch-all)
url: '/inbox/{inboxId:.*}'

注意,不能在路由中使用捕獲組

甚至能夠在路徑中使用查詢參數。

// will match a route such as
// /inbox?sort=ascending
url: '/inbox?sort'

絕對路由

若是你使用絕對 url 方式,須要在 url 字符串的開發加上特殊字符 ^

$stateProvider
  .state('contacts', {
     url: '/contacts',
     ...
  })
  .state('contacts.list', {
     url: '^/list',
     ...
  });
  • 'contacts'狀態將匹配"/contacts"
  • 'contacts.list'狀態將匹配"/list"。子狀態的url沒有附在父狀態的url以後的,由於使用了^

嵌套路由 咱們可使用 url 參數添加到路由中來實現嵌套路由。這樣能夠提供多個 ui-views 在咱們的頁面中,例如,咱們能夠在 /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 之下的子路由,語法 (.) 表示這是一個子路由。

/inbox/1 匹配第一個狀態,/inbox/1/priority 則匹配第二個狀態。使用這種語法,咱們能夠在父路由中支持嵌套的 url。在父視圖中的 ui-view 指令將會處理 priority。

Params

params 選項是參數名稱或者正則的數組。它不能合併 url 選項,當狀態激活的時候,應用會使用這些參數填充 $stateParams 服務。

Views

咱們能夠在 state 中提供命名的視圖。這是強大的特性,在單個視圖中,咱們能夠定義多個視圖,甚至使用單個模板。

若是咱們使用了 views 參數,那麼,templateUrl, template 和 templateProvider 就會忽略。若是咱們但願包含父模板,咱們須要建立一個抽象模板。

假如咱們有以下模板。

<div>
  <div ui-view="filters"></div>
  <div ui-view="mailbox"></div>
  <div ui-view="priority"></div>
</div>

 主要注意的是,頂級的狀態天然對應母版頁中的視圖,通常這個視圖是 noname 的,因此,須要一個 noname 的視圖來匹配這個 placeholder。其它的視圖須要匹配父狀態中的視圖 placeholder,這些 placeholder 能夠是命名的,也能夠是 naname的,天然,noname 的只能有一個,不然沒法進行區分,咱們在 provider 中進行配置的時候,就須要描述清楚這些 view 和 placeholder 之間的對應關係。

使用 @ 能夠定義絕對命名的視圖名稱,@ 的前面是 placeholder 的名稱,後面是狀態的名稱。@ 前面爲空表示未命名的 ui-view,@ 後面爲空表示相對於根模板,一般是 index.html

咱們能夠建立命名的視圖,而後填充對應的模板。每一個子視圖均可以有特有的模板,控制器和數據。

$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 來嵌套多個路由,例如,全部路由都在 /admin 下面。

$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

在應用進入或者退出視圖的時候,會調用這些回調函數。它們均可以設置回調函數;函數能夠訪問獲取的數據。

這些回調函數能夠提供咱們一些能力,在訪問新視圖,或者改變當前狀態的時候。這裏是很好的執行 "Are you sure?" 對話框,或者請求用戶在進入以前登錄的地方。

兩個函數都不提供參數,須要的信息須要本身提供。

Data

咱們能夠附加任意的數到咱們的狀態配置對象 configObject 上,data 屬性相似於 resolve 屬性,除了不會注入到控制器,也不會 resolve promise。

當須要從父狀態向子狀態傳遞數據的時候,附加數據是方便的途徑。

Evnets

相似 ngRoute 服務,angular-route 服務在狀態生命週期的不一樣時間點會觸發多種事件。咱們能夠經過在 $scope 中監聽來處理這些事件。

全部的下面的事件都會在 $rootScope 中觸發,因此,咱們能夠在任何 $scope 對象中監聽這些事件。

State change events

能夠以下監聽

$scope.$on('$stateChangeStart', 
function(evt, toState, toParams, fromState, fromParams), {
  // We can prevent this state from completing
  evt.preventDefault();
});

$stateChangeStart

當從一個狀態開始向另一個狀態過分的時候觸發。

$stateChangeSuccess

當狀態過渡完成以後觸發。

$stateChangeError

在狀態過渡中出現錯誤。常見的錯誤例如,不能獲取模板,或者 promise 不能成功 resolve 等等

視圖加載事件

ui-router 也提供了視圖加載階段的事件。

$viewContentLoading

視圖開始加載,可是,在 DOM 渲染以前。

能夠以下監聽。

$scope.$on('$viewContentLoading', 
function(event, viewConfig){ 
    // Access to all the view config properties.
    // and one special property 'targetView'
    // viewConfig.targetView 
});

$viewContentLoad

視圖已經加載,渲染完成。

$stateParams

在前面的內容中,咱們使用 $stateParams 來從 url 參數中獲取 params ,這個服務,與 url 不一樣。

例如,若是咱們 inbox 狀態的 url 以下。

url: '/inbox/:inboxId/messages/{sorted}?from&to'

用戶使用下面的 url 訪問

/inbox/123/messages/ascending?from=10&to=20

咱們的 $stateParams 對象將獲取以下數據。

{inboxId: '123', sorted: 'ascending', from: 10, to: 20}

$urlRouterProvider

相似 ngRoute, 能夠建立當特定的 url 訪問時處理的規則。

能夠經過不一樣的 url 激活不一樣的狀態,因此在管理激活和加載狀態的時候, $urlRouterProvider 並非必須的。在狀態管理以外的時候纔會須要,好比重定向,或者驗證的時候。

when()

when 函數須要兩個參數,咱們但願匹配的路徑,另外就是咱們但願從新定向的目標。也能夠是一個函數。

例如,若是但願任何空的路由到咱們的 /inbox 路由中。

.config(function($urlRouterProvider) {
  $urlRouterProvider.when('', '/inbox');
});

若是提供一個函數處理,路由匹配的時候,這個函數就會被調用,它能夠返回下列三種之一的結果。

  • false,這個迴應告訴 $urlRouter 規則並不匹配,應該查找其它匹配的狀態,在咱們但願驗證用戶是否訪問正確地址的時候頗有用。
  • 字符串,$urlRouter 將其做爲重定向目標。
  • true 或者 undefined,函數已經處理了這個 url 

otherwise()

與 ngRoute 中的 oterwise() 方法相似,oterwiese() 在沒有其它路由匹配的狀況下重定向。這是建立默認 url 的好方法。

otherwise() 函數只須要一個參數,一個字符串或者一個函數。

若是提供了一個字符串,就會被看作一個默認地址,在任何錯誤的或者不能匹配任何路由的時候,就會被重定向到這個地址。

若是是一個函數,在沒有其它路由匹配的時候,就會被執行

.config(function($urlRouterProvider) {
  $urlRouterProvider.otherwise('/');
  // or
  $urlRouterProvider.otherwise(
    function($injector, $location) {
      $location.path('/');
    });
});

rule()

若是咱們但願處理任何路由,或者在其它路由以前進行一些處理,可使用 rule() 函數。

咱們必須返回一個驗證的路徑串

app.config(function($urlRouterProvider){
  $urlRouterProvider.rule(
    function($injector, $location) {
      return '/index';
    });
})

 激活狀態

有三種方式來激活特定的狀態

  • 使用 $state.go() 方法
  • 使用 ui-sref 綁定的鏈接
  • 直接導航到與狀態關聯的 url

 

建立報名嚮導

爲何不使用一下它呢?

咱們建立一個報名的嚮導來演練一下 ui-router 的使用。

使用 ui-router ,咱們建立一個簡單的報名服務,使用一個控制器來處理報名。

首先,咱們建立應用的視圖。

<div ng-controller="WizardSignupController">
  <h2>Signup wizard</h2>
  <div ui-view></div>
</div>

在這個視圖中,咱們定義了報名視圖。下一步,在報名嚮導中,須要三步

  • start,在這一步,咱們獲取用戶名稱,提供歡迎信息
  • email, 這裏,咱們獲取用戶的 email 信息
  • finish, 這裏,用戶完成報名,咱們簡單地顯示完成頁面

報名處理依賴 wizardapp.controllers 模塊,

angular.module('wizardApp', [
  'ui.router',
  'wizardapp.controllers'
  ]);

咱們的 wizardSignupController 控制器,使用 $scope.user 對象在整個過程當中收集信息。

angular.module('wizardapp.controllers', [])
.controller('WizardSignupController', 
  ['$scope', '$state', function($scope, $state) {
    $scope.user = {};
    $scope.signup = function() {}
}]);

如今,嚮導處理邏輯處理主要工做,配置 config() 

angular.module('wizardApp', [
  'ui.router', 'wizardapp.controllers'
  ])
.config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
  $stateProvider
    .state('start', {
      url: '/step_1',
      templateUrl: 'partials/wizard/step_1.html'
    })
    .state('email', {
      url: '/step_2',
      templateUrl: 'partials/wizard/step_2.html'
    })
    .state('finish', {
      url: '/finish',
      templateUrl: 'partials/wizard/step_3.html'
    });
}]);

這樣,咱們基本的流程就已經有了。如今,若是用戶導航到 /step_1,就會看到開始頁面,儘管如今地址是 /step_1, 而咱們但願是 /wizard/step_1

爲了這個效果,咱們建立 abstract 狀態來寄宿各個步驟。

.config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
  $stateProvider
    .state('wizard', {
      abstract: true,
      url: '/wizard',
      template: '<div><div ui-view></div></div>'
    })
    .state('wizard.start', {
      url: '/step_1',
      templateUrl: 'partials/wizard/step_1.html'
    })
    .state('wizard.email', {
      url: '/step_2',
      templateUrl: 'partials/wizard/step_2.html'
    })
    .state('wizard.finish', {
      url: '/finish',
      templateUrl: 'partials/wizard/step_3.html'
    });
}]);

這樣,它們都安全地嵌套到 /wizard 之下了。

在狀態之間進行導航,咱們使用 ui-router 提供的指令 ui-sref 來生成連接,這個指令用來生成導航連接。

例如,step_1.html 以下。

<!-- step_1.html -->
<h3>Step 1</h3>
<form ng-submit="">
  <input type="text" ng-model="user.name" placeholder="Your name" />
  <input type="submit" class="button" value="Next" ui-sref="wizard.email"/>
</form>

咱們還但願在報名流程完成以後,執行特定的動做,來調用定義在父控制器上的 signup 函數,咱們能夠在最後的步驟中添加一個控制器來調用 $scope.signup() 函數。因爲整個嚮導封裝在 WizardSignupControoler 中,咱們能夠像一般同樣訪問嵌套的 scope 對象。

.state('wizard.finish', {
  url: '/finish',
  templateUrl: 'partials/wizard/step_3.html',
  controller: function($scope) {
    $scope.signup();
  }
});

總結

在這裏,咱們深刻討論了 ui-router 幾乎所有的特性,咱們發現這個庫很是有用,但願也能幫到你。

相關文章
相關標籤/搜索