1. 程式人生 > >angular路由詳解

angular路由詳解

angular路由

路由 (route) ,幾乎所有的 MVC(VM) 框架都應該具有的特性,因為它是前端構建單頁面應用 (SPA) 必不可少的組成部分。

那麼,對於 angular 而言,它自然也有 內建 的路由模組:叫做 ngRoute 。

不過,大家很少用它,因為它的功能太有限,往往不能滿足開發需求!!

於是,一個基於 ngRoute 開發的 第三方路由模組 ,叫做 ui.router ,受到了大家的“追捧”。

ngRoute vs ui.router

首先,無論是使用哪種路由,作為框架額外的附加功能,它們都將以 模組依賴 的形式被引入,簡而言之就是:在引入路由 原始檔

 之後,你的程式碼應該這樣寫(以 ui.router 為例):

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

這樣做的目的是: 在程式啟動(bootstrap)的時候,載入依賴模組(如:ui.router),將所有 掛載 在該模組的 服務(provider) , 指令(directive) , 過濾器(filter) 等都進行註冊 ,那麼在後面的程式中便可以呼叫了。

說到這裡,就得看看 ngRoute模組 和 ui.router模組 各自都提供了哪些服務,哪些指令?

  1. ngRoute
  2. $routeProvider(服務提供者) --------- 對應於下面的urlRouterProvider和stateProvider
  3. $route(服務) --------- 對應於下面的urlRouter和state
  4. $routeParams(服務) --------- 對應於下面的stateParams
  5. ng-view(指令) --------- 對應於下面的ui-view

  6. ui.router

  7. $urlRouterProvider(服務提供者) --------- 用來配置路由重定向
  8. $urlRouter(服務)
  9. $stateProvider(服務提供者) --------- 用來配置路由
  10. $state(服務) --------- 用來顯示當前路由狀態資訊,以及一些路由方法(如:跳轉)
  11. $stateParams(服務) --------- 用來儲存路由匹配時的引數
  12. ui-view(指令) --------- 路由模板渲染,對應的dom相關聯
  13. ui-sref(指令)
  14. ...

 : 服務提供者:用來提供服務例項和配置服務。 )

這樣一看,其實 ui.router 和 ngRoute 大體的設計思路,對應的模組劃分都是一致的(畢竟是同一個團隊開發),不同的地方在於功能點的實現和 增強 。

那麼問題來了: ngRoute 弱在哪些方面, ui.router 怎麼彌補了這些方面?

這裡,列舉兩個最重要的方面來說(其他細節,後面再說):

  1. 多檢視
  2. 巢狀檢視

多檢視

多檢視:頁面可以顯示多個動態變化的不同區塊。

這樣的業務場景是有的:

比如:頁面一個區塊用來顯示頁面狀態,另一個區塊用來顯示頁面主內容,當路由切換時,頁面狀態跟著變化,對應的頁面主內容也跟著變化。

首先,我們嘗試著用 ngRoute 來做:

html

<div ng-view>區塊1</div>
<div ng-view>區塊2</div>

js

$routeProvider
    .when('/', {
    template: 'hello world'
});

我們在html中利用ng-view指令定義了兩個區塊,於是兩個div中顯示了相同的內容,這很合乎情理,但卻不是我們想要的,但是又不能為力,因為,在ngRoute中:

  1. 檢視沒有名字進行唯一標誌,所以它們被同等的處理。
  2. 路由配置只有一個模板,無法配置多個。

ok,針對上述兩個問題,我們嘗試用 ui.router 來做:

html

  <div ui-view></div>
  <div ui-view="status"></div>

js

$stateProvider
	.state('home', {
		url: '/',
		views: {
			'': {
				template: 'hello world'
			},
			'status': {
				template: 'home page'
			}
		}
	});

這次,結果是我們想要的,兩個區塊,分別顯示了不同的內容,原因在於,在ui.router中:

  1. 可以給檢視命名,如:ui-view="status"。
  2. 可以在路由配置中根據檢視名字(如:status),配置不同的模板(其實還有controller等)。

 :檢視名是一個字串,不可以包含 @ (原因後面會說)。

巢狀檢視

巢狀檢視:頁面某個動態變化區塊中,巢狀著另一個可以動態變化的區塊。

這樣的業務場景也是有的:

比如:頁面一個主區塊顯示主內容,主內容中的部分內容要求根據路由變化而變化,這時就需要另一個動態變化的區塊巢狀在主區塊中。

其實,巢狀檢視,在html中的最終表現就像這樣:

<div ng-view>
I am parent
<div ng-view>I am child</div>
</div>

轉成javascript,我們會在程式裡這樣寫:

$routeProvider
    .when('/', {
    template: 'I am parent <div ng-view>I am child</div>'
});

倘若,你真的用 ngRoute 這樣寫,你會發現瀏覽器崩潰了,因為在ng-view指令link的過程中,程式碼會無限遞迴下去。

那麼造成這種現象的最根本原因: 路由沒有明確的父子層級關係!

看看 ui.router 是如何解決這一問題的?

$stateProvider
	.state('parent', {
		abstract: true,
		url: '/',
		template: 'I am parent <div ui-view></div>'
	})
	.state('parent.child', {
		url: '',
		template: 'I am child'
	});

  1. 巧妙地,通過 parent 與 parent.child 來確定路由的 父子關係 ,從而解決無限遞迴問題。
  2. 另外子路由的模板最終也將被插入到父路由模板的div[ui-view]中去,從而達到檢視巢狀的效果。

ui.router工作原理

路由,大致可以理解為:一個 查詢匹配 的過程。

對於前端 MVC(VM) 而言,就是將 hash值 (#xxx)與一系列的 路由規則 進行查詢匹配,匹配出一個符合條件的規則,然後根據這個規則,進行資料的獲取,以及頁面的渲染。

所以,接下來:

  • 第一步,學會如何建立路由規則?
  • 第二步,瞭解路由查詢匹配原理?

路由的建立

首先,看一個簡單的例子:

$stateProvider
    .state('home', {
        url: '/abc',
        template: 'hello world'
    });

上面,我們通過呼叫 $stateProvider.state(...) 方法,建立了一個簡單路由規則,通過引數,可以容易理解到:

  1. 規則名:'home'
  2. 匹配的url:'/abc'
  3. 對應的模板:'hello world'

意思就是說:當我們訪問 http://xxxx#/abc 的時候,這個路由規則被匹配到,對應的模板會被填到某個 div[ui-view] 中。

看上去似乎很簡單,那是因為我們還沒有深究具體的一些路由配置引數(我們後面再說)。

這裡需要深入的是: $stateProvider.state(...) 方法,它做了些什麼工作?

  1. 首先,建立並存儲一個state物件,裡面包含著該路由規則的所有配置資訊。
  2. 然後,呼叫 $urlRouterProvider.when(...) 方法,進行路由的 註冊 (之前是路由的建立),程式碼裡是這樣寫的:
$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,

  1. 當程式匹配到某一個子路由時,我們就認為這個子路由state被啟用,同時,它對應的父路由state也將被啟用。
  2. 我們還可以手動的啟用某一個state,就像上面寫的那樣, $state.transitionTo(state, ...); ,這樣的話,它的父state會被啟用(如果還沒有啟用的話),它的子state會被銷燬(如果已經啟用的話)。

ok,回到之前的路由註冊,呼叫了 $urlRouterProvider.when(...) 方法,它做了什麼呢?

它建立了一個rule,並存儲在rules集合裡面,之後的,每次hash值變化,路由重新查詢匹配都是通過遍歷這個 rules 集合進行的。

路由的查詢匹配

有了之前,路由的建立和註冊,接下來,自然會想到路由是如何查詢匹配的?

恐怕,這得從頁面載入完畢說起:

  1. angular 在剛開始的$digest時, $rootScope 會觸發 $locationChangeSuccess 事件(angular在每次瀏覽器hash change的時候也會觸發 $locationChangeSuccess事件)
  2. ui.router 監聽了 $locationChangeSuccess 事件,於是開始通過遍歷一系列rules,進行路由查詢匹配
  3. 當匹配到路由後,就通過 $state.transitionTo(state,...) ,跳轉啟用對應的state
  4. 最後,完成資料請求和模板的渲染

可以從下面這段原始碼看到,看到查詢匹配的起始和過程:

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嗎?

答案是:肯定的,一般的路由器都是這麼做的,包括ngRoute。

那麼ui.router對於這樣的問題,會怎麼進行 優化 呢?

迴歸到問題:我們之所以要迴圈遍歷rules,是因為要查詢匹配到對應的路由(state),然後跳轉過去,倘若不迴圈,能直接找到對應的state嗎?

答案是:可以的。

還記得前面說過,在用ui.router在建立路由時:

  1. 會例項化一個對應的state物件,並存儲起來(states集合裡面)
  2. 每一個state物件都有一個state.name進行唯一標識(如:'home')

根據以上兩點,於是ui.router提供了另一個指令叫做: ui-sref指令 ,來解決這個問題,比如這樣:

<a ui-sref="home">通過ui-sref跳轉到home state</a>

當點選這個a標籤時,會直接跳轉到home state,而並不需要迴圈遍歷rules,ui.router是這樣做到的(這裡簡單說一下):

首先,ui-sref="home"指令會給對應的dom新增 click事件 ,然後根據state.name,直接跳轉到對應的state,程式碼像這樣:

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

跳轉到對應的state之後,ui.router會做一個善後處理,就是改變hash,所以理所當然,會觸發’$locationChangeSuccess'事件,然後執行回撥,但是在回撥中可以通過一個判斷程式碼規避迴圈rules,像這樣:

function update(evt) {
  var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;

  // 手動呼叫$state.go(...)時,直接return避免下面的迴圈
  if (ignoreUpdate) return true;

  // 省略下面的迴圈ruls程式碼
}

說了那麼多,其實就是想說,我們 不建議直接使用href="#/xxx"來改變hash ,然後跳轉到對應state(雖然也是可以的),因為這樣做會多了一步rules迴圈遍歷,浪費效能,就像下面這樣:

<a href="#/abc">通過href跳轉到home state</a>

路由詳解

這裡詳細地介紹ui.router的引數配置和一些深層次用法。

不過,在這之前,需要一個demo,ui.router的 官網demo 無非就是最好的學習例子,裡面涉及了大部分的知識點,所以接下來的程式碼講解大部分都會是這裡面的(建議下載到本地進行程式碼學習)。

為了更好的學習這個demo,我畫了一張圖來描述這個demo的contacts部分各個檢視模組,如下:

父與子

之前就說到,在ui.router中,路由就有父與子的關係(多個父與子湊起來就有了,祖先和子孫的關係),從javascript的角度來說,其實就是路由對應的state物件之間存在著某種 引用 的關係。

用一張資料結構的表示下contacts部分,大概是這樣( 原圖 ):

上面的圖看著有點亂,不過沒關係,起碼能看出各個state物件之間通過 parent 欄位維繫了這樣一個 父與子 的關係(粉紅色的線)。

ok,接下來就看下是如何定義路由的父子關係的?

假設有一個父路由,如下:

$stateProvider
.state('contacts', {});

ui.router提供了幾種方法來定義它的子路由:

1.點標記法( 推薦 )

$stateProvider
.state('contacts.list', {});

通過 狀態名 簡單明瞭地來確定父子路由關係,如:狀態名為'a.b.c'的路由,對應的父路由就是狀態名為'a.b'路由。

2. parent 屬性

$stateProvider
.state({
name: 'list',// 狀態名也可以直接在配置裡指定
parent: 'contacts'// 父路由的狀態名
});

或者:

$stateProvider
.state({
name: 'list',// 狀態名也可以直接在配置裡指定
parent: {// parent也可以是一個父路由配置物件(指定路由的狀態名即可)
name: 'contacts'
}
});

通過 parent 直接指定父路由,可以是父路由的狀態名(字串),也可以是一個包含狀態名的父路由配置(物件)。

竟然路由有了 父與子 的關係,那麼它們的註冊順序有要求嘛?

答案是:沒有要求,我們可以在父路由存在之前,建立子路由(不過,不是很推薦),因為ui.router在遇到這種情況時,在內部會幫我們先 快取 子路由的資訊,等待它的父路由註冊完畢後,再進行子路由的註冊。

模板渲染

當路由成功跳轉到指定的state時,ui.router會觸發 '$stateChangeSuccess' 事件通知所有的 ui-view 進行模板重新渲染。

程式碼是這樣的:

if (options.notify) {
  $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
}

而 ui-view 指令在進行 link 的時候,在其內部就已經監聽了這一事件(訊息),來隨時更新檢視:

scope.$on('$stateChangeSuccess', function() {
  updateView(false);
});

大體的模板渲染過程就是這樣的,這裡遇到一個問題,就是:每一個 div[ui-view]在重新渲染的時候如何獲取到對應檢視模板的呢?

要想知道這個答案,

首先,我們得先看一下模板如何設定?

一般在設定 單檢視 的時候,我們會這樣做:

$stateProvider
.state('contacts', {
abstract: true,
url: '/contacts',
templateUrl: 'app/contacts/contacts.html'
});

在配置物件裡面,我們用 templateUrl 指定模板路徑即可。

如果我們需要設定 多檢視 ,就需要用到 views欄位 ,像這樣:

$stateProvider
.state('contacts.detail', {
url: '/{contactId:[0-9]{1,4}}',
views: {
'' : {
templateUrl: 'app/contacts/contacts.detail.html',
},
'[email protected]': {
template: 'This is contacts.detail populating the "hint" ui-view'
},
'menuTip': {
templateProvider: ['$stateParams', function($stateParams) {
return '<hr><small class="muted">Contact ID: ' + $stateParams.contactId + '</small>';
}]
}
}
});

這裡我們使用了另外兩種方式設定模板:

  1. template :直接指定模板內容,另外也可以是函式返回模板內容
  2. templateProvider :通過依賴注入的呼叫函式的方式返回模板內容

上述我們介紹了設定 單檢視 和 多檢視 模板的方式,其實最終它們在ui.router內部都會被統一格式化成的 

相關推薦

angular路由四(子路由

str edr bsp 絕對路徑 pat outer menu one const 子路由是相對路由 路由配置部分: 主要是children const routes: Routes = [ {path:‘home‘, component: HomeComponent,

angular路由

angular路由 路由 (route) ,幾乎所有的 MVC(VM) 框架都應該具有的特性,因為它是前端構建單頁面應用 (SPA) 必不可少的組成部分。 那麼,對於 angular 而言,它自然也有 內建 的路由模組:叫做 ngRoute 。 不過,大家很少用

angular 路由

ui.router詳解,對比ngRoute,可以使用父子路由,兩者同時變化,也可以使用命名路由angular.module('myApp', []).     config(['$routeProvider', function($routeProvider) {      

angular指令--自定義指令

修飾 cor ring ttr 子元素 標簽 常用 文本 利用 自定義指令 directive()這個方法是用來定義指令的: angular.module(‘myApp‘, []) .directive(‘myDirective‘, function ($timeout,

elasticsearch系列三:索引(分詞器、文檔管理、路由

ces com dex 合並 pda ams 最新 case dbi 一、分詞器 1. 認識分詞器 1.1 Analyzer 分析器 在ES中一個Analyzer 由下面三種組件組合而成: character filter :字符過濾器,對文本進行字符過濾處理,

Asp.net 路由

網路上關於asp.net路由知識的一些資源: https://www.cnblogs.com/cklovefan/p/7785307.html https://blog.csdn.net/slowlifes/article/details/72461440   一、請求一個

react router @4 和 vue路由 (二)react-router @4用法

  完整版:https://www.cnblogs.com/yangyangxxb/p/10066650.html   2、react-router @4用法   a、大概目錄        不需要像vue那樣麻煩的用到

react router @4 和 vue路由 (一)vue路由基礎和使用

完整版:https://www.cnblogs.com/yangyangxxb/p/10066650.html 1、vue路由基礎和使用   a、大概目錄            我這裡建了一個router資料夾,資料夾下有index.html

react router @4 和 vue路由 (七)react路由守衛

完整版:https://www.cnblogs.com/yangyangxxb/p/10066650.html   12、react路由守衛?   a、在之前的版本中,React Router 也提供了類似的 onEnter 鉤子,但在 React Router 4.0 版

react router @4 和 vue路由 (六)vue怎麼通過路由傳參?

完整版:https://www.cnblogs.com/yangyangxxb/p/10066650.html 8、vue怎麼通過路由傳參?   a、萬用字元傳引數 //在定義路由的時候 { path: '/describe/:id', name: 'Desc

Ocelot簡易教程(三)之主要特性及路由

上篇《Ocelot簡易教程(二)之快速開始2》教大家如何快速跑起來一個ocelot例項專案,也只是簡單的對Ocelot進行了配置,這篇文章會給大家詳細的介紹一下Ocelot的配置資訊。希望能對大家深入使用Ocelot有所幫助。 上篇中也提到了,最簡單的Ocelot如下面所示,只有簡單的兩個節點,一個是ReR

Spring Cloud系列(二十四) 路由(Finchley.RC2版本)

傳統路由配置 傳統路由配置就是不需要依賴服務發現機制,通過在配置檔案中具體指定每個路由表示式與服務例項的對映關係來實現API閘道器對外請求的路由。 單例項配置 通過zuul.routes.<route>.path與zuul.routes.<route&

ThinkPHP 路由

https://doqrsj.coding.io/index.php/index/index/index?aaa=1   index.php為根目錄首頁引導檔案,其意義在於訪問index.php時定位到application目錄 https://doqrsj.coding.

springcloud系列—Zuul—第5章-2: Spring Cloud Zuul 路由

資料參考:《Spring Cloud 微服務實戰》 目錄 路由詳解 傳統路由配置 服務路由配置 服務路由的預設規則 自定義路由對映關係 路徑匹配 忽略表示式 路由字首 本地跳轉 cookie與頭資訊 重定向問題 路由詳解 傳統路由配置

angularJS路由

angular路由: 1.ng-route 講解   路由功能是由 routeProvider服務 和 ng-view 搭配實現,ng-view相當於提供了頁面模板的掛載點,當切換URL進行跳轉時,不同的頁面模板會放在ng-view所在的位置; 然後通過 rou

Laravel路由

在寫路由時,可以直接使用一條路由,代替一個一個的寫路由: Route::resource('tasks', 'TasksController'); =========等價於========》》》》》》》》 Route::get('/tasks', '[email protected]

ionic入門教程第十九課-ionic路由(state、route、resolve)

今天好好的跟大家講講ionic的路由配置。 問到的朋友有點多,因為這個內容比較多,所以我一直想等我多瞭解一些再出關於路由的教程。 但是有些很簡單的也有朋友不理解,所以我就提前出了這篇教程。 希望能對大家有點幫助,關於我遺漏的部分,後面再發教程補充吧。 但是基本的內容,簡單的

angular directive

在前端開發的過程中,很多時候我們都希望能夠重複的使用某一個模組,比如說檔案上傳、組織架構樹等,angular的directive可以幫助我們輕鬆的實現元件的重複使用。 首先先讓我們解釋一個問題:什麼是directive? directive是DOM元素的一個標記(可以是屬

Express應用程式之路由

路由路由是指如何定義應用的端點(URIs)以及如何響應客戶端的請求。路由是由一個 URI、HTTP 請求(GET、POST等)和若干個控制代碼組成,它的結構如下: app.METHOD(path, [callback...], callback), app 是 express

前端路由

什麼是前端路由? 路由是根據不同的 url 地址展示不同的內容或頁面,就是把不同路由對應不同的內容或頁面的任務交給前端來做,之前是通過服務端根據 url 的不同返回不同的頁面實現的。 什麼時候使用前端路由? 在單頁面應用中,大部分頁面結構不變,只改變部