Angular學習筆記(7)—多重視圖和路由

??能夠從頁面的一個視圖跳轉到另外一個視圖,對單頁面應用來講是至關重要的。
??除了用ng-include指令在視圖中引用多個模板外,更好的做法是將視圖分解成布局和模板視圖,并且根據用戶當前訪問的URL來展示對應的視圖。
??我們會將這些模板分解到視圖中,并在布局模板內進行組裝。AngularJS允許我們在$route服務的提供者$routeProvider中通過聲明路由來實現這個功能。

安裝

??從1.2版本開始,AngularJS將ngRoutes從核心代碼中剝離出來成為獨立的模塊。我們需要安裝并引用它,才能夠在AngularJS應用中正常地使用路由功能。
??可以從http://code.angularjs.org 下載它,然后保存到一個可以在HTML頁面中進行引用的位置。也可以用Bower來安裝,這樣會將它存放到Bower的目錄中。

$bower instal l--save angular-route

??在HTML中,需要在AngularJS之后引用angular-route

<script src="js/vendor/angular.js"></script>
<script src="js/vendor/angular-route.js"></script>

??最后,要把ngRoute模塊在我們的應用中當作依賴加載進來:

angular.module('myApp', ['ngRoute']);

布局模版

??要創建一個布局模板,需要修改HTML以告訴AngularJS把模板渲染到何處。通過將ng-view指令和路由組合到一起,我們可以精確地指定當前路由所對應的模板在DOM中的渲染位置。
例如:

<header>
    <h1>Header</h1>
</header>
<div class="content">
    <div ng-view></div>
</div>
<footer>
    <h5>Footer</h5>
</footer>

這個例子中,我們將所有需要渲染的內容都放到了<div class="content">中,而<header><footer>中的內容在路由改變時不會有任何變化。
ng-view是由ngRoute模塊提供的一個特殊指令,它的獨特作用是在HTML中給$route對應的視圖內容占位。
它會創建自己的作用域并將模板嵌套在內部。
ng-view是一個優先級為1000的終極指令。AngularJS不會運行同一個元素上的低優先級指令(例如<div ng-view></div>元素上其他指令都是沒有意義的)。
ngView指令遵循以下規則。

  • 每次觸發$routeChangeSuccess事件,視圖都會更新。
  • 如果某個模板同當前的路由相關聯:
  • 創建一個新的作用域;
  • 移除上一個視圖,同時上一個作用域也會被清除;
  • 將新的作用域同當前模板關聯在一起;
  • 如果路由中有相關的定義,那么就把對應的控制器同當前作用域關聯起來;
  • 觸發$viewContentLoaded事件;
  • 如果提供了onload屬性,調用該屬性所指定的函數。

路由

??我們可以使用AngularJS提供的whenotherwise兩個方法來定義應用的路由。用config函數在特定的模塊或應用中定義路由。

angular.module('myApp', []).config(['$routeProvider', function($routeProvider) {
    // 在這里定義路由
}]);

??現在,我們可以用when方法來添加一個特定的路由。這個方法可以接受兩個參數。
下面的例子展示了如何創建一個獨立的路由:

angular.module('myApp', []).config(['$routeProvider', function($routeProvider) {
    $routeProvider.when('/', {
        templateUrl: 'views/home.html',
        controller: 'HomeController'
    });
}]);

??第一個參數是路由路徑,這個路徑會與$location.path進行匹配,$location.path也就是
當前URL的路徑。如果路徑后面還有其他內容,或使用了雙斜線也可以正常匹配。我們可以在URL中存儲參數,參數需要以冒號開頭(例如:name),用$routeParams可以讀取這些參數。
第二個參數是配置對象,決定了當第一個參數中的路由能夠匹配時具體做些什么。配置對象中可以進行設置的屬性包括controllertemplatetemplateURLresolveredirectToreloadOnSearch

angular.module('myApp', []).
    config(['$routeProvider', function($routeProvider) {
        $routeProvider
          .when('/', {
            templateUrl: 'views/home.html',
            controller: 'HomeController'
          })
          .when('/login', {
            templateUrl: 'views/login.html',
            controller: 'LoginController'
          })
          .when('/dashboard', {
            templateUrl: 'views/dashboard.html',
            controller: 'DashboardController',
            resolve: {
                user: function(SessionService) {
                    return SessionService.getCurrentUser();
                }
            }
          })
          .otherwise({
            redirectTo: '/'
          });
    }]);
1.controller
controller: 'MyController'
// 或者
controller: function($scope) {}

??如果配置對象中設置了controller屬性,那么這個指定的控制器會與路由所創建的新作用域關聯在一起。如果參數值是字符型,會在模塊中所有注冊過的控制器中查找對應的內容,然后與路由關聯在一起。如果參數值是函數型,這個函數會作為模板中DOM元素的控制器并與模板進行關聯。

2.template
template: '<div><h2>Route</h2></div>'

??AngularJS會將配置對象中的HTML模板渲染到對應的具有ng-view指令的DOM元素中。

3.templateUrl
templateUrl: 'views/template_name.html'

??應用會根據templateUrl屬性所指定的路徑通過XHR讀取視圖(或者從$templateCache中讀取)。如果能夠找到并讀取這個模板,AngularJS會將模板的內容渲染到具有ng-view指令的DOM元素中。

4.resolve
resolve: {
    'data': ['$http', function($http) {
        return $http.get('/api').then(
            function success(resp) { return response.data; },
            function error(reason) { return false; }
        );
    }];
}

??如果設置了resolve屬性,AngularJS會將列表中的元素都注入到控制器中。如果這些依賴是promise對象,它們在控制器加載以及$routeChangeSuccess被觸發之前,會被resolve并設置成一個值。
列表對象可以是:

  • 鍵,鍵值是會被注入到控制器中的依賴的名字;
  • 工廠,即可以是一個服務的名字,也可以是一個返回值,它是會被注入到控制器中的函數或可以被resolvepromise對象。

??在上面的例子中,resolve會發送一個$http請求,并將data的值替換為返回結果的值。列表中的鍵data`會被注入到控制器中,所以在控制器中可以使用它。

5.redirectTo
redirectTo: '/home'
// 或者
redirectTo: function(route,path,search)

??如果redirectTo屬性的值是一個字符串,那么路徑會被替換成這個值,并根據這個目標路徑觸發路由變化。
??如果redirectTo屬性的值是一個函數,那么路徑會被替換成函數的返回值,并根據這個目標路徑觸發路由變化。
??如果redirectTo屬性的值是一個函數,AngularJS會在調用它時傳入下面三個參數中:
??(1) 從當前路徑中提取出的路由參數;
??(2) 當前路徑;
??(3) 當前URL中的查詢串。

6.reloadOnSearch

??如果reloadOnSearch選項被設置為true(默認),當$location.search()發生變化時會重新加載路由。如果設置為false,那么當URL中的查詢串部分發生變化時就不會重新加載路由。
??下面的例子中設置了兩個路由:一個首頁路由和一個收件箱路由,同時首頁路由被設置成默認路由。

angular.module('MyApp', []).config(['$routeProvider', function($routeProvider) {
    $routeProvider.when('/', {
        controller: 'HomeController',
        templateUrl: 'views/home.html'
    })
    .when('/inbox/:name', {
        controller: 'InboxController',
        templateUrl: 'views/inbox.html'
    })
    .otherwise({redirectTo: '/'});
}]);

??otherwise方法會在沒有任何路由匹配時被調用,我們用它設置了一個默認跳轉到'/'路徑的路由。
??當瀏覽器加載AngularJS應用時,會將URL設置成默認路由所指向的路徑。除非我們在瀏覽器中加載不同的URL,否則默認會使用'/'路由。

$routeParams

??如果我們在路由參數的前面加上:,AngularJS就會把它解析出來并傳遞給$routeParams
例如,如果我們設置下面這樣的路由:

$routeProvider
  .when('/inbox/:name', {
      controller: 'InboxController',
      templateUrl: 'views/inbox.html'
  });

??AngularJS會在$routeParams中添加一個名為name的鍵,它的值會被設置為加載進來的URL中的值。
??如果瀏覽器加載/inbox/all這個URL,那么$routeParams對象看起來會是這樣:{ name: 'all' }。需要注意,如果想要在控制器中訪問這些變量,需要把$routeParams注入進控制器:

app.controller('InboxController', function($scope,$routeParams) {
     // 在這里訪問$routeParams
});

$location 服務

??AngularJS提供了一個服務用以解析地址欄中的URL,并讓你可以訪問應用當前路徑所對應的路由。它同樣提供了修改路徑和處理各種形式導航的能力。
??$location服務對JS中的window.location對象的API進行了更優雅地封裝,并且和AngularJS集成在一起。
??當應用需要在內部進行跳轉時是使用$location服務的最佳場景,比如當用戶注冊后、修改或者登錄后進行的跳轉。
??$location服務沒有刷新整個頁面的能力。如果需要刷新整個頁面,需要使用$window.location對象(window.location的一個接口)。

1.path()

path()用來獲取頁面當前的路徑:

$location.path(); // 返回當前路徑

修改當前路徑并跳轉到應用中的另一個URL:

$location.path('/'); // 把路徑修改為'/'路由

path()方法直接和HTML5的歷史API進行交互,所以用戶可通過點擊后退按鈕退回到上一個頁面。

2. replace()

如果你希望跳轉后用戶不能點擊后退按鈕(對于登錄之后的跳轉這種發生在某個跳轉之后的再次跳轉很有用),AngularJS提供了replace()方法來實現這個功能:

$location.path('/home');
$location.replace();
// 或者
$location.path('/home').replace();
3. absUrl()

absUrl()方法用來獲取編碼后的完整URL:

$location.absUrl()
4. hash()

hash()方法用來獲取URL中的hash片段:

$location.hash(); // 返回當前的hash片段
5. host()

host()方法用來獲取URL中的主機:

$location.host();// 當前URL的主機
6. port()

port()方法用來獲取URL中的端口號:

$location.port();// 當前URL的端口
7. protocol()

protocol()方法用來獲取URL中的協議:

$location.protocol();// 當前URL的協議
8. search()

search()方法用來獲取URL中的查詢串:

$location.search();

我們可以向這個方法中傳入新的查詢參數,來修改URL中的查詢串部分:

// 用對象設置查詢
$location.search({name: 'Ari', username: 'auser'});
// 用字符串設置查詢
$location.search('name=Ari&username=auser');

search方法可以接受兩個參數。

  • search(可選,字符串或對象)
    這個參數代表新的查詢參數。hash對象的值可以是數組。
  • paramValue(可選,字符串)
    如果search參數的類型是字符串,那么paramValue會做為該參數的值覆蓋URL當中的對應值。如果paramValue的值是null,對應的參數會被移除掉。
9. url()

url()方法用來獲取當前頁面的URL:

$location.url(); // 該URL的字符串

如果調用url()方法時傳了參數,會設置并修改當前的URL,這會同時修改URL中的路徑、查詢串和hash,并返回$location。

// 設置新的URL
$location.url('/home?name=Ari#hashthing');

url()方法可以接受兩個參數:

  • url(可選,字符串)
    新的URL的基礎的前綴。
  • replace(可選,字符串)
    想要修改成的路徑。

路由模式

??不同的路由模式在瀏覽器的地址欄中會以不同的URL格式呈現。$location服務默認會使用標簽模式來進行路由。
??路由模式決定你的站點的URL長成什么樣子。

標簽模式

??標簽(hashbang)是AngularJS用來同你的應用內部進行鏈接的技巧。標簽模式是HTML5模式的降級方案,URL路徑會以#符號開頭。標簽模式不需要重寫<a href=""></a>標簽,也不需要任何服務器端的支持。如果沒有進行額外的指定,AngularJS將默認使用標簽模式。
使用標簽模式的URL看起來是這樣的:

http://yoursite.com/#!/inbox/all

??如果要顯式指定配置并使用標簽模式,需要在應用模塊的config函數中進行配置:

angular.module('myApp', ['ngRoute'])
.config(['$locationProvider', function($locationProvider) {
$locationProvider.html5Mode(false);
}]);

我們還可以配置hashPrefix,也就是標簽模式下標簽默認的前綴!符號。這個前綴也是AngularJS在比較老的瀏覽器中降級機制的一部分。這個符號是可以配置的:

angular.module('myApp', ['ngRoute'])
  .config(['$locationProvider', function($locationProvider) {
      $locationProvider.html5Mode(false);
      $locationProvider.hashPrefix('!');
  }]);

HTML5 模式

??AngularJS支持的另外一種路由模式是html5模式。在這個模式中,URL看起來和普通的URL
一樣。例如,同樣的路由在HTML5模式中看起來是這樣的:http://yoursite.com/inbox/all
??在AngularJS內部,$location服務通過HTML5歷史API讓應用能夠使用普通的URL路徑來路由。當瀏覽器不支持HTML5歷史API時,$location服務會自動使用標簽模式的URL作為替代方案。
??$location服務還有一個有趣的功能,當一個支持HTML5歷史API的現代瀏覽器加載了一個帶標簽的URL時,它會為用戶重寫這個URL。
??在HTML5模式中,AngularJS會負責重寫<a href=""></a>中的鏈接。也就是說AngularJS會
根據瀏覽器的能力在編譯時決定是否要重寫href=""中的鏈接。
??例如<a href="/person/42?all=true">Person</a>這個標簽,在老式瀏覽器中會被重寫成標簽模式的URL:/index.html#!/person/42?all=true。但在現代瀏覽器中會URL會保持本來的樣子。
??后端服務器也需要支持URL重寫,服務器需要確保所有請求都返回index.html,以支持HTML5模式。這樣才能確保由AngularJS應用來處理路由。
??當在HTML5模式的AngularJS中寫鏈接時,永遠都不要使用相對路徑。如果你的應用是在根路徑中加載的,這不會有什么問題,但如果是在其他路徑中,AngularJS應用就無法正確處理路由了。
另一個選擇是在HTML文檔的HEAD中用<base>標簽來指定應用的基礎URL:

<base href="/base/url" />

路由事件

??$route服務在路由過程中的每個階段都會觸發不同的事件,可以為這些不同的路由事件設置監聽器并做出響應。這個功能對于控制不同的路由事件,以及探測用戶的登錄和授權狀態等場景是非常有用的。
??我們需要給路由設置事件監聽器,用$rootScope來監聽這些事件。

1. $routeChangeStart

??AngularJS在路由變化之前會廣播$routeChangeStart事件。在這一步中,路由服務會開始加載路由變化所需要的所有依賴,并且模板和resolve鍵中的promise也會被resolve

angular.module('myApp', [])
  .run(['$rootScope', '$location', function($rootScope, $location) {
      $rootScope.$on('$routeChangeStart', function(evt, next, current) {
      });
  }]);

$routeChangeStart事件帶有兩個參數:

  • 將要導航到的下一個URL;
  • 路由變化前的URL。
2. $routeChangeSuccess

??AngularJS會在路由的依賴被加載后廣播$routeChangeSuccess事件。

angular.module('myApp', [])
  .run(['$rootScope', '$location', function($rootScope, $location) {
      $rootScope.$on('$routeChangeSuccess', function(evt, next, previous) {
      });
  }]);

$routeChangeStart事件帶有三個參數:

  • 原始的AngularJS evt對象;
  • 用戶當前所處的路由;
  • 上一個路由(如果當前是第一個路由,則為undefined)。
3. $routeChangeError

??AngularJS會在任何一個promise被拒絕或者失敗時廣播$routeChangeError事件。

angular.module('myApp', [])
  .run(function($rootScope, $location) {
      $rootScope.$on('$routeChangeError', function(current, previous, rejection) {
      });
  });

$routeChangeError事件有三個參數:

  • 當前路由的信息;
  • 上一個路由的信息;
  • 被拒絕的promise的錯誤信息。
4. $routeUpdate

??AngularJS在reloadOnSearch屬性被設置為false的情況下,重新使用某個控制器的實例時,會廣播$routeUpdate事件。

關于搜索引擎索引

??Web爬蟲對于JS的胖客戶端應用無能為力。為了在應用的運行過程中給爬蟲提供支持,我們需要在頭部添加meta標簽。這個元標記會讓爬蟲請求一個帶有空的轉義片段參數的鏈接,服務器根據請求返回對應的HTML代碼片段。

<meta name="fragment" content="!"/>

更多關于路由的內容

頁面重新加載

??$location服務不會重新加載整個頁面,它只會單純地改變URL。如果我們想重新加載整個頁面,需要用$window服務來設置地址。

$window.location.href = "/reload/page";

異步的地址變化

??如果我們想要在作用域的生命周期外使用$location服務,必須用$apply函數將變化拋到應用外部。因為$location服務是基于$digest來驅動瀏覽器的地址變化,以使路由事件正常工作的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容