AngularJs功能(九)--路由

何爲路由

路由機制運能夠實現多視圖的單頁Web應用(single page web application,SPA)。html

單頁應用在使用期間不會從新加載頁面,全部的數據交互由ajax完成。前端

非單頁應用請求不一樣url,對SEO很不友善,並且刷新或發送url沒法保留瀏覽進度。html5

而單頁應用前端擁有監管url的權利,經過監聽頁面地址變化來調用前端MVC模塊,渲染不一樣頁面。固然,前端這時也擁有了修改URL的能力,只須要管理好前端視圖與URL。在肯定了須要保持的頁面後,先後端圍繞頁面各自負責視圖的渲染。web

路由實現

url不變話化

這一種的狀況是url徹底不變,即你的頁面怎麼改變,怎麼跳轉url都不會改變。
這種狀況的原理 就是純ajax拿到頁面模板後替換原頁面中的元素,重現渲染頁面。然而ajax的一個致命缺點就是致使瀏覽器後退按鈕失效。用戶體驗很很差。ajax

使用hash值

這種類型的優勢就是刷新頁面,頁面也不會丟。關於hash值的介紹連接
經過監聽 hash(#)的變化來執行js代碼 從而實現 頁面的改變 bootstrap

核心代碼:後端

window.addEventListener('hashchange',function(){
//code
})

就是經過這個原理 只要#改變了 就能觸發這個事件,這也是不少單頁面網站的url中都也 (#)的緣由api

h5 history api

這種類型是經過html5的最新history api來實現的。
經過pushState()記錄操做歷史,監聽window.onpopstate事件來進行視圖切換,能正常的回退前進。
這裏是關於 history api的鏈接promise

關於這三種方法的demo日後會給你們提供完善,你們能夠提醒我。瀏覽器

AngularJS 路由

AngularJS 路由就是經過經過hash和h5 history兩種方式實現的,容許咱們經過不一樣的 URL 訪問不一樣的內容。

一般咱們的URL形式爲 http://runoob.com/first/page,但在單頁Web應用中 AngularJS 經過 # + 標記 實現,例如:

http://cds.com/#/firstPage
http://cds.com/#/secondPage

當咱們點擊以上的任意一個連接時,向服務端請的地址都是同樣的 (http://runoob.com/)。 由於 # 號以後的內容在向服務端請求時會被瀏覽器忽略掉。 因此咱們就須要在客戶端實現 # 號後面內容的功能實現。

AngularJS 路由經過 # + 標記 幫助咱們區分不一樣的邏輯頁面並將不一樣的頁面(View)綁定到對應的控制器(controller)上。

下面咱們介紹一下Angular的路由實現。

ngRouter

ngRouter是angular的一個單獨模塊。
咱們看看他是怎麼實現的。
一、加載實現路由的 js 文件:angular-route.js。
二、包含了 ngRoute 模塊做爲主應用模塊的依賴模塊。(爲何這麼寫,下文介紹)

angular.module('routingDemoApp',['ngRoute'])

三、使用 ngView 指令。

<div ng-view></div>

該 div 內的 HTML 內容會根據路由的變化而變化。

四、配置 $routeProvider,AngularJS $routeProvider 用來定義路由規則。

angular.module('MyApp', ['ngRoute']).config(['$routeProvider', function($routeProvider){
  //$routeProvider 爲咱們提供了 when(path,object) & otherwise(object) 函數按順序定義全部路由,函數包含兩個參數:第一個參數是 URL 或者 URL 正則規則。第二個參數是路由配置對象。
  $routeProvider.when(url, {
    template: string,//在 ng-view 中插入簡單的 HTML 內容
    templateUrl: string,//在 ng-view 中插入 HTML 模板文件
    controller: string, function 或 array,//在當前模板上執行的controller函數,生成新的scope。
    controllerAs: string,//爲controller指定別名。
    redirectTo: string, function,//重定向的地址。
    resolve: object<key, function>//指定當前controller所依賴的其餘模塊。
  });
  $routeProvider.otherwise({redirectTo:'/'})//除配置路由外的其餘路徑指向
}]);

UI-Router

在使用ngRouter時每每不能進行視圖嵌套,並且功能有限,不能知足開發需求。由此咱們就須要另外一個第三方路由模塊,叫作 ui.router ,固然它是基於ngRouter開發的。
ui.Router提供了一種很好的機制,能夠實現深層次嵌套。

實現(配置 )

一、在引入ui.route路由源文件

二、加載依賴模塊

angular.module("myApp", ["ui.router"]); 
// myApp爲自定義模塊,依賴第三方路由模塊ui.router

在程序啓動(bootstrap)的時候,加載依賴模塊(如:ui.router),將全部 掛載 在該模塊的 服務(provider) , 指令(directive) , 過濾器(filter) 等都進行註冊 ,那麼在後面的程序中即可以調用了。

angular.module("myApp", ["ui.router","myFilter","myDirective","myService"]);
//實際中只要依賴首頁須要用的模塊就能夠。

三、定義視圖

<div ui-view></div>//視圖展現
<a ui-sref='dash'>dash頁</a>//定義導航

在視圖模板中還能夠包含本身的 ui-view ,這就是咱們能夠支持嵌套路徑的緣由。

<div ui-view>
    <div ui-view></div>
</div>

四、配置 $stateProvider,在ui.router經過$stateProvider來定義路由規則

angular.module("MyApp").config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {
   //$stateProvider爲咱們提供了state(pathName,object) & otherwise(object) 函數按順序定義全部路由,函數包含兩個參數:第一個參數是 URL名稱(視圖中導航名稱)。第二個參數是路由配置對象。
   $urlRouterProvider.otherwise("/dash.html");
   //定義默認跳轉和除配置以外的定向路由
   $stateProvider
       .state('dash'(string), {//導航用的名字,如<a ui-sref="dash">dash</a>裏的login
           url: "/dash.html"(string),//訪問路徑,可視的路徑
           template: string,//在 ui-view 中插入簡單的 HTML 內容
           templateUrl: "views/dash.html"(string),//在 ui-view 中插入 HTML 模板文件(靜態文件路徑)
           data: {pageTitle: "系統主頁"}(object<key,val>能夠多層嵌套),//data數據不會注入到控制器,能夠從父狀態向子狀態傳遞數據
           controller: "DashController",//在當前模板上執行的controller函數,生成新的scope。
           params:object,//params 選項是參數名稱(下文有栗子)。當狀態激活的時候,應用會使用這些參數填充 $stateParams 服務。
           resolve: {
               deps: ['$ocLazyLoad', function ($ocLazyLoad) {//'$ocLazyLoad'是controller的依賴模塊,返回執行方法。AngularJs 經過 ocLazyLoad 實現動態(懶)加載模塊和依賴。具體的有時間再擴展。留個連接!!!
                 return $ocLazyLoad.load({
                    name: 'MetronicApp',
                    insertBefore: '#ng_load_plugins_before', 
                    files: [
                        'app/controllers/DashController.js',
                    ]
                 });
              }]
          }//在下文介紹
    })
}]);
resolve 功能
使用 resolve 功能,咱們能夠準備一組用來注入到控制器中的依賴對象,在 uiRoute 中,resolve 能夠在路由實際渲染以前解決掉 promise。
resolve 選項提供一個對象,對象中的 key 就是準備注入 controller 的依賴名稱,值則是建立對象的工廠。
若是是一個字符串,就試圖用這個串來匹配當前已經註冊的服務名稱,
若是是一個函數,執行這個函數,返回的值就是依賴。
若是函數返回一個 promise,在控制器被實例化以前,將會被 resolved,返回的值被注入到 controller中。

路由嵌套

先說說爲何要視圖嵌套,頁面一個主區塊顯示主內容,主內容中的部份內容要求根據路由變化而變化,這時就須要另外一個動態變化的區塊嵌套在主區塊中。

若是用ngRoute 實現視圖嵌套,你會發現是不可能的,由於在ng-view指令link的過程當中,代碼會無限遞歸下去。形成這種現象的最根本緣由:路由沒有明確的父子層級關係。
而uiRouter很好的解決了這一問題。
慣例先上代碼:

$stateProvider
  .state('parent', {
      abstract: true,//能夠經過它解決依賴問題,或者特定數據處理,或者簡單地一樣的 url 來嵌套多個路由,例如,全部路由都在 /parent下面。
      url: '/parent',//相對路徑=>…./index.html#/parent
      template: 'I am parent <div ui-view></div>'//固然你也能夠寫在templateUrl中
  })
  .state('parent.child', {
      url: '/child',//相對路徑=>…./index.html#/parent/child
      url:'^/child',//絕對路徑=>…./index.html#/child    注意寫法區別
      template: 'I am child'
  });

<!--herf --> 
<a ui-sref="parent">點我顯示父view內容</a>
<a ui-sref="parent.child">點我顯示父view與子view內容</a>
<!--view --> 
<div ui-view></div> <!-- 父View -->

巧妙地,經過 parent 與 parent.child 來肯定路由的 父子關係 ,從而解決無限遞歸問題。另外子路由的模板最終也將被插入到父路由模板的div[ui-view]中去,從而達到視圖嵌套的效果。

多視圖效果

多視圖:頁面能夠顯示多個動態變化的不一樣區塊。
緣由在於,在ui.router中:能夠給視圖命名(字符串),如:ui-view="status"。能夠在路由配置中根據視圖名字(如:status),配置不一樣的模板(其實還有controller等)。

放一段代碼

<div ng-app="myApp" >
    <a ui-sref="index">點我顯示index內容</a>
    <div ui-view="header"></div>  
    <div ui-view="nav"></div>  
    <div ui-view="body"></div>      
</div>  

var app = angular.module('myApp', ['ui.router']);   
app.config(["$stateProvider",  function ($stateProvider) {      
    $stateProvider     
    .state("index", {
        url: '/index',  
        views:{
            'header':{template:"<div>頭部內容</div>"},
            'nav':{template:"<div>菜單內容</div>"},
            'body':{template:"<div>展現內容</div>"}
        }
    })      

}]);

待完善(項目無涉及)(控制起來也很麻煩,處理很差都是坑,若是要用到再完善吧。)

視圖定位

@的做用 是用來絕對定位view,即說明該ui-view屬於哪一個模板。
一樣放一段代碼

<div ng-app="myApp" >
    <a ui-sref="index">show index</a>
    <a ui-sref="index.content1">content111111</a>
    <a ui-sref="index.content2">content222222</a>
    <div ui-view="index"><div>             
</div>  


var app = angular.module('myApp', ['ui.router']);   
app.config(["$stateProvider",  function ($stateProvider) {      
    $stateProvider     
    .state("index", {
        url: '/index',  
        views:{
            'index':{template:"<div><div ui-view='header'></div>  <div ui-view='nav'></div> <div ui-view='body'></div>  </div>"},
            //這裏必需要絕對定位
            'header@index':{template:"<div>頭部內容header</div>"},
            'nav@index':{template:"<div>菜單內容nav</div>"},
            'body@index':{template:"<div>展現內容contents</div>"}
        }
    })    
    //絕對定位
    .state("index.content1", {
        url: '/content1',  
        views:{
            'body@index':{template:"<div>content11111111111111111</div>"}
            //'body@index'表時名爲body的view使用index模板
        }
    })  
    //相對定位:該狀態的裏的名爲body的ui-view爲相對路徑下的(即沒有說明具體是哪一個模板下的)
    .state("index.content2", {
        url: '/content2',  
        views:{
            'body':{template:"<div>content2222222222222222222</div>"}//
        }
    })      

}]);

待完善(項目無涉及)(其實也不難理解,只是這個時間段任務重了,這裏有時間再瞭解完善啊)。

路由傳參

Angular應用經過經過$stateParams服務獲取參數他有兩種方式:(相對溫習一下路徑穿參方式,$location.search())
1.url: '/index/:id'
2.url: '/index/{id}'
注意這裏只是兩種不一樣寫法

angular.module("MetronicApp").config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {
$stateProvider
    .state('dash', {
        url: "/dash.html/:orderId/:projectId",
        //或者注意是或者(這種須要直接定義好,或由頁面傳值)
        url:"/dash.html/{orderId}",
        //若是應用訪問 /dash.html/42,那麼,$stateParameter.orderId 就成爲 42, 實際上, $stateParams 的值將爲 { orderId: 42 }
        //或者(這種是在路由中定義好參數)
        url:"/dash.html",
        params: {
          orderId: { value: 42}
        }
        templateUrl: "/dash.html",
        controller: function ($stateParams,$scope) { 
          //能夠注入$stateParams服務來獲取你所傳遞的服務
          $scope.projectId=$stateParams.projectId; 
          $scope.orderId=$stateParams.orderId; //42 
        }  
    })
}]);
//而後你們在看頁面中的寫法
也是兩種方式建議用第二種()
<a href="#/dash/42" >href傳參數</a> 
<a ui-sref="dash({'orderId':42,'projectId':256})">ui-sref傳參數</a>

再來擴展一下,在控制器中的頁面跳轉,而且傳參

angular.module("MetronicApp",['ui.router']);  
angular.module("MetronicApp").controller('MyCtrl', function($scope, $state,$location) {  
      $scope.pathChange = function() {  
         $state.go('dash',{name: 42}); //第一個參數是定義的urlName,第二個爲參數
      };  
      $scope.pathChange = function() {  
         $location.path('/sixth/detail/42'); //$location服務中有介紹
      };  
});

可能你們會有疑問,咱們項目的路徑和這個示例不同,這裏的參數是angular是控制路徑跳轉的參數目的在與定向視圖或者視圖之間丶控制器之間的通訊,不是從後臺請求的,這個要搞清楚。

和以前說的$rootScope通訊效果是同樣的。

監聽路由

Angular 路由狀態發生改變時能夠經過'$stateChangeStart'、'$stateChangeSuccess'、'$stateChangeError'監聽,經過注入'$location'實現狀態的管理。
代碼示例以下:

function run($ionicPlatform, $location, Service, $rootScope, $stateParams) {  
       //路由監聽事件  
       $rootScope.$on('$stateChangeStart',  
               function(event, toState, toParams, fromState, fromParams) {  
                   console.log(event);  //該事件的基本信息
                   console.log(toState);  //咱們能夠獲得當前路由的信息,好比路由名稱,url,視圖的控制器,模板路徑等等
                   console.log(toParams); //咱們能夠獲得當前路由的參數 
                   console.log(fromState);  //咱們能夠獲得上一個路由的信息,好比路由名稱,url,視圖的控制器,模板路徑等等
                   console.log(fromParams); //咱們能夠獲得上一個路由的參數 

                   if (toState.name == "dash") {  
                       //獲取參數以後能夠調請求判斷須要渲染什麼頁面,渲染不一樣的頁面經過 $location 實現  
                       if (toParams.id == 10) {  
                           //$location.path();//獲取路由地址  
                           // $location.path('/validation').replace();  
                           // event.preventDefault()能夠阻止模板解析  
                       }  
                   }  
               })  
       // stateChangeSuccess  當模板解析完成後觸發  
       $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {  
         //code     
       })      
       // $stateChangeError  當模板解析過程當中發生錯誤時觸發  
       $rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error) {  
         //code
       })  
   }

在頁面渲染中 可經過'$viewContentLoading'和 '$viewContentLoaded'監聽頁面渲染狀態:渲染開始和渲染結束。(在控制器中添加如下代碼實現監聽)

// $viewContentLoading- 當視圖開始加載,DOM渲染完成以前觸發,該事件將在$scope鏈上廣播此事件。  
$scope.$watch('$viewContentLoading',function(event, viewConfig){  
   alert('模板加載完成前');  
});  
//$viewContentLoaded- 當視圖加載完成,DOM渲染完成以後觸發,視圖所在的$scope發出該事件。  
$scope.$watch('$viewContentLoaded',function(event){   
     alert('模板加載完成後');  
});

咱們能夠用這些監聽事件來判斷用戶傳參,權限斷定等等。

工做原理

咱們瞭解一下uiRouter路由的工做原理
大體能夠理解爲:一個 查找匹配 的過程。就是將 hash值 (#xxx)與一系列的 '路由規則' 進行查找匹配,匹配出一個符合條件的規則,而後根據這個規則,進行數據的獲取,以及頁面的渲染。
咱們分兩步學習

路由的建立

咱們經過調用 $stateProvider.state(...) 方法,建立了一個簡單路由規則(詳情看上文)
當咱們反問http://...index.html#/dash.html的時候,這個路由規則被匹配到,對應的模板會被填到某個 [ui-view] 中。
它作了些什麼呢。首先,建立並存儲一個state對象,裏面包含着該路由規則的全部配置信息。
而後,調用 $urlRouterProvider.when(...) 方法(上文說過ui-router是基於ngRouter),進行路由的 註冊 (以前是路由的建立),代碼裏是這樣寫的:

$urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {
  // 判斷是不是同一個state || 當前匹配參數是否相同
  if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {
     $state.transitionTo(state, $match, { inherit: true, location: false });
  }
}]);

當 hash值 與 state.url 相匹配時,就執行後面那段回調,回調函數裏面進行了兩個條件判斷以後,決定是否須要跳轉到該state。

至於說爲何說 "跳轉到該state,而不是該url"? 其實 ui.router是基於state(狀態)的,而不是url,以前就說過,路由存在着明確的 父子關係 ,每個路由能夠理解爲一個state,當程序匹配到某一個子路由時,咱們就認爲這個子路由state被激活,同時,它對應的父路由state也將被激活。咱們還能夠手動的激活某一個state,就像上面寫的那樣, $state.transitionTo(state, ...) ,這樣的話,它的父state會被激活(若是尚未激活的話),它的子state會被銷燬(若是已經激活的話)。

接着回到路由註冊,路由註冊調用了 $urlRouterProvider.when(...) 方法,它建立了一個rule,並存儲在rules集合裏面,以後的,每次hash值變化,路由從新查找匹配都是經過遍歷這個 rules 集合進行的。

路由的查找匹配

當路由的建立和註冊,接下來,就是路由的查找匹配了。這是一個複雜而又繁瑣的過程,繞到我都有點不想說。
angular 在剛開始的$digest(解析,髒查詢,能作的事情不少)時, $rootScope 會觸發 $locationChangeSuccess 事件(angular在每次瀏覽器hash change的時候也會觸發 $locationChangeSuccess事件)ui.router 監聽了 $locationChangeSuccess 事件,因而開始經過遍歷一系列rules,進行路由查找匹配當匹配到路由後,就經過 $state.transitionTo(state,...) ,跳轉激活對應的state最後,完成數據請求和模板的渲染
來上一段源碼

function update(evt) {
  // ...省略
  function check(rule) {
    var handled = rule($injector, $location);
    // handled能夠是返回:
    // 1. 新的的url,用於重定向
    // 2. false,不匹配
    // 3. true,匹配
    if (!handled) return false;

    if (isString(handled)) $location.replace().url(handled);
    return true;
  }

  var n = rules.length, i;

  // 渲染遍歷rules,匹配到路由,就中止循環
  for (i = 0; i < n; i++) {
    if (check(rules[i])) return;
  }
  // 若是都匹配不到路由,使用otherwise路由(若是設置了的話)
  if (otherwise) check(otherwise);
}

function listen() {
  // 監聽$locationChangeSuccess,開始路由的查找匹配
  listener = listener || $rootScope.$on('$locationChangeSuccess', update);
  return listener;
}

if (!interceptDeferred) listen();

看懂的朋友就會發現一個問題,每次路由變化(hash變化),因爲監聽‘$locationChangeSuccess'事件,都要進行rules的遍歷 來查找匹配路由,而後跳轉到對應的state。咱們之因此要循環遍歷rules,是由於要查找匹配到對應的路由(state),而後跳轉過去,假若不循環,也是能直接找到對應的state。在用ui.router在建立路由時:會實例化一個對應的state對象,並存儲起來(states集合裏面)。每個state對象都有一個state.name進行惟一標識(如:'dash')。這時候就體現出 ui-sref指令的大做用了。

<a ui-sref="dash">經過ui-sref跳轉到dash.html</a>

當點擊這個a標籤時,會直接跳轉到dash.html,而並不須要循環遍歷rules。這個元素折行了一個方法,仍是直接看代碼

element.bind("click", function(e) {
 // ..
  var transition = $timeout(function() {
    // 手動跳轉到指定的state
    $state.go(ref.state, params, options);
  });
});

ui-sref="dash"指令會給對應的dom添加 click事件 ,而後根據dash(state.name),直接跳轉到對應的state。

跳轉到對應的state以後,ui.router會作一個善後處理,就是改變hash,此時就會觸發’$locationChangeSuccess'事件,而後執行回調,可是在回調中能夠經過一個判斷代碼規避循環rules。
代碼段:

function update(evt) {
  var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;
  // 手動調用$state.go(...)時,直接return避免下面的循環
 if (ignoreUpdate) return true;
}

因此咱們在使用中可經過ui-serf來實現路由,達到視圖切換,或者在controller中調用 $state.go(....)來實現。

結語

路由介紹的就這麼多,可是任然要仔細逐行閱讀。也有一部分拓展,項目中沒有實際用到,可是也算是給優化作一些思路。與你們共勉。

相關文章
相關標籤/搜索