angular學習筆記(三十一)-$location(2)

詩&遠方發表於2014-12-03

之前已經介紹了$location服務的基本用法:angular學習筆記(三十一)-$location(1).

這篇是上一篇的進階,介紹$location的配置,相容各版本瀏覽器,等.

*注意,這裡介紹的是基於angular-1.3.2版本的,低版本的$location可能會有問題.

 

hashbang模式和history api建立單頁應用

首先,$location是用在單頁應用裡的...(廢話,angular就是用在單頁的)...所以,$location處理的是url改變,但是不重新整理頁面的情況.那麼我們知道,不重新整理頁面但是請求ajax改變url,需要存入歷史記錄.這樣的話,需要使用html5的history api,但是對於不支援history api的瀏覽器(也就是ie8,9吧,反正ie7angular本來就不支援,而且看了花瓣網,它在ie7,8,也沒有使用單頁,而是新開連結),則需要使用hashbang模式.

什麼叫hashbang模式?在網上查閱了很多也沒有查到具體的說明.只能按照自己理解的來總結:

比如一個url: http://localhost:801/$location/index.html,這個頁面,在垃圾瀏覽器裡,需要實現傳送ajax,改變url,但是不重新整理頁面,並且可以使用後退前進按鈕,怎麼做呢? 就在url後面使用'#'加一個識別符號'!',再加上路徑,引數,雜湊值等...這樣,因為使用了'#',所以頁面不會重新整理,而url也改變了,可以存入歷史記錄.其中'#'代表了hash,'!'代表了bang,所以這種模式被稱為hashbang模式.注意,'!'不是固定的,可以是任意的識別符號,也可以為空的.而且,當我重新整理http://localhost:801/$location/index.html#!/foo?name=code_bunny#bunny時,可以訪問到http://localhost:801/$location/index.html.

這就是我自己總結的傳統的hashbang模式.而使用HTML5 history api,則不需要'#'和標示符.只需要直接在url後面加上路徑,引數,雜湊值,等...但是當我重新整理http://localhost:801/$location/index.html/foo?name=code_bunny#bunny時,是不能訪問到http://localhost:801/$location/index.html頁面的,需要服務端進行配置,使應用能夠接受來自http://localhost:801/$location/index.html/foo?name=code_bunny#bunny的請求.

介紹完了hashbang和history api,接下來來看下angular是怎麼處理它們的.

 

配置$location服務

可以看到,在 angular學習筆記(三十一)-$location(1)這篇文章裡,所有例子的url都是帶有#的,(bang識別符號為空),無論在任何瀏覽器裡,它都是這樣的,而沒有使用history api. 如果需要使用history api,或者配置bang識別符號,可以對$location服務進行配置:

var locationApp = angular.module('locationApp',[]);
locationApp.config(function($locationProvider){
    $locationProvider.html5Mode(true).hashPrefix('!');
});

$locationProvider有兩個方法可以配置:

$locationProvider.html5Mode(boolean || obj)

1.$locationProvider.html5Mode(true): 開啟html5的history api

2.$locationProvider.html5Mode(false): 關閉html5的history api

3.$locationProvider.html5Mode({enabled:true}): 同1

4.$locationProvider.html5Mode({enabled:false}): 同2(預設配置)

5.$locationProvider.html5Mode({requireBase:true}): 設定為'需要定義base href'標籤(後面會講到base href標籤)

6.$locationProvider.html5Mode({requireBase:false}): 設定為'不需要定義base href'標籤(後面會講到base href標籤)

$locationProvider.hashPrefix('string')

設定bang識別符號.不設定的話,預設為空

 

base href屬性的定義 

在介紹hashbang和histroy api的時候,我把url分成了兩部分顏色顯示,第一部分是淺綠色的,第二部分是深綠色的.淺綠色的是固定不變的,而深綠色的,是可以改變的.也就是$location.url()

那麼瀏覽器是如何知道固定的是什麼的呢? 就是通過設定base href屬性:

在head標籤裡新增如下標籤:

<base href="/$location/base/">

這樣,瀏覽器就知道,不變的部分是 http://localhost:801/$location/base/.注意,一定要有'/'開頭,'/'結尾,否則在垃圾瀏覽器裡會有問題.因為它不會自己給它加上'/',但如果你定義的base href是'/$location/base/index.html',那是可以的.

另外,定義了這個base href以後,頁面裡所有的js,css,都是這個base href的相對路徑.

前面提到了$locationProvider.html5Mode的引數的requireBase屬性,就是用來定義是否頁面中一定要定義base href標籤的.

如果不定義,它會自己把頁面的根目錄作為固定部分,比如 http://localhost:801/$location/base/index.html, 它會認為固定部分是http://localhost:801

總結一下,base href的值,應該是域名部分後面的整個路徑部分,以/開始,/結尾,注意一定要加這個屬性!!!實際工作中很容易忘記加它導致路由不生效也不報錯.被坑過很多次了!!!

 

a連結的跳轉

頁面的url改變,肯定是點選了某些元素,最常見的自然是a連結,a連結本來的作用是跳轉頁面,但在單頁應用中,我們只需要讓它改變url後面的$location.url()部分,而不重新整理頁面.所以,a連結可以這麼寫:

<a href="some1?foo=bar">/some1?foo=bar</a> 
<a href="some2?foo=bar#myhash">/some2?foo=bar#myhash</a> 

angular會自動處理瀏覽器的相容問題.假設點選第一個連結:

在高階瀏覽器裡,url會變成: http://localhost:801/$location/base/some1?foo=bar

在垃圾瀏覽器裡,url會變成: http://localhost:801/$location/base/#!/some1?foo=bar

注意事項:

1.href值以'/'開頭,會跳轉重新整理頁面.

2.a連結帶有target屬性,會跳轉重新整理頁面.

3.外鏈的話,直接寫絕對地址即可跳轉重新整理頁面

 

$location的雙向資料繫結 

下面這段程式碼演示瞭如何實現讓$location.url和位址列的url雙向繫結:

<input type="text" ng-model="location" ng-model-options="{getterSetter:true}"/>
$scope.location=function(newLocation){
    return $location.url(newLocation);
};

 

綜合例項

最後,我用所有關於$location的知識寫一個demo:

這個例項在angular學習筆記(三十一)-$location(1)的基礎上新增了本篇講到的知識:

1.新增base href標籤

2.hashbang和html5 history api處理瀏覽器相容

3.$location雙向資料繫結

4.a連結改變url

 

html:

<!DOCTYPE html>
<html ng-app="locationApp">
<head>
  <title>21.1 $location</title>
  <meta charset="utf-8">
  <base href="/$location/base/">
  <script src="../angular-1.3.2.js"></script>
  <script src="../script.js"></script>
</head>

<body ng-controller="locationCtrl">
<input type="text" ng-model="location" ng-model-options="{getterSetter:true}"/> <p>完整url路徑: <span>{{absurl}}</span></p> <p>url路徑(當前url#後面的內容,包括引數和雜湊值): <span>{{url}}</span> <button ng-click="changeUrl()">改變</button> </p> <p>相對路徑(也就是當前url#後面的內容,不包括引數): <span>{{path}}</span> <button ng-click="changePath()">改變</button> </p> <p>協議(比如http,https): <span>{{protocol}}</span></p> <p>主機名: <span>{{host}}</span></p> <p>埠號: <span>{{port}}</span></p> <p>雜湊值: <span>{{hash}}</span> <button ng-click="changeHash()">改變</button> </p> <p>search值序列化json: <span>{{search}}</span> <button ng-click="changeSearch_1()">改變1</button> <button ng-click="changeSearch_2()">改變2</button> <button ng-click="changeSearch_3()">改變3</button> <button ng-click="changeSearch_4()">改變4</button> <button ng-click="changeSearch_5()">改變5</button> <button ng-click="changeSearch_6()">改變6</button> </p> <a href="some1?foo=bar">/some1?foo=bar</a> | <a href="some2?foo=bar#myhash">/some2?foo=bar#myhash</a> | <a href="http://www.baidu.com">外部連結</a>

</body> </html>

js:

var locationApp = angular.module('locationApp',[]);
locationApp.config(function($locationProvider){
    $locationProvider.html5Mode(true).hashPrefix('!');
});
locationApp.controller('locationCtrl',function($scope,$location,$timeout,$rootScope){
    $scope.location=function(newLocation){
        return $location.url(newLocation);
    };
    $scope.absurl = $location.absUrl();
    $scope.url = $location.url();
    $scope.path = $location.path();
    $scope.protocol = $location.protocol();
    $scope.host = $location.host();
    $scope.port = $location.port();
    $scope.hash = $location.hash();
    $scope.search = $location.search();

    $scope.refresh = function(){
        $scope.absurl = $location.absUrl();
        $scope.url = $location.url();
        $scope.path = $location.path();
        $scope.hash = $location.hash();
        $scope.search = $location.search();
    };

    //重寫url部分,相應的absurl,url,path,hash,search都會改變
    $scope.changeUrl = function(){
        $location.url('/foo2?name=bunny2&age=12#myhash2');
    };

    //重寫path部分,相應的absurl,url,path都會改變
    $scope.changePath = function(){
        $location.path('/foo2/foo3');
    };

    //重寫hash部分,相應的absurl,url,hash都會改變
    $scope.changeHash = function(){
        $location.hash('myhash3');
    };

    //修改search部分(方法1),相應的absurl,url,search,hash都會改變
    //指定兩個引數,第一個引數是屬性名,第二個引數是屬性值.
    //如果屬性名是已有屬性,則修改,如果屬性名不是已有的,則新增.
    //屬性值也可以是一個陣列,參考方法6
    $scope.changeSearch_1 = function(){
        $location.search('name','code_bunny');
    };

    //修改search部分(方法2),相應的absurl,url,search,hash都會改變
    //指定兩個引數,第二個引數是null:刪除第一個引數所指定的屬性名.不再有這個屬性
    //若第一個引數不是已有的,則不發生任何改變
    $scope.changeSearch_2 = function(){
        $location.search('age',null);
    };

    //修改search部分(方法3),相應的absurl,url,search,hash都會改變
    //指定一個引數,json物件,直接重寫整個search部分.不管是不是已有屬性,全部重寫.
    //這裡屬性的值可以是一個陣列,參考方法5
    $scope.changeSearch_3 = function(){
        $location.search({name:'papamibunny',age:16,love:'zxg'});
    };

    //修改search部分(方法4),相應的absurl,url,search,hash都會改變
    //指定一個引數,字串,整個search部分就變為這個字串.注意是沒有屬性值的.
    $scope.changeSearch_4 = function(){
        $location.search('bunnybaobao');
    };

    //修改search部分(方法5),相應的absurl,url,search,hash都會改變
    //其餘和方法3一樣.全部重寫search:
    //指定一個引數,json格式,屬性值是一個陣列,那麼最後的search會變成name=code_bunny&name=white_bunny&name=hua_bunny
    $scope.changeSearch_5 = function(){
        $location.search({name:['code_bunny','white_bunny','hua_bunny']});
    };

    //修改search部分(方法6),相應的absurl,url,search,hash都會改變
    //其餘和方法1一樣,修改指定的屬性名(或新增)
    //第二個引數是一個陣列,最後search中的love部分會變成love=zxg&love=mitu
    //它和方法5的區別,就像方法1和方法3的區別,一個是修改或新增某個屬性值,一個是重置整個search
    $scope.changeSearch_6 = function(){
        $location.search('love',['zxg','mitu']).replace();
    };

    //使用$location.replace(),則這一次的修改路徑不會被記錄到歷史記錄中,點選後退,不會後退到改變前的路徑,而是後退到改變前的路徑的改變前的路徑

    $rootScope.$on('$locationChangeStart',function(){
        console.log('開始改變$location')
    });
    $rootScope.$on('$locationChangeSuccess',function(){
        $scope.refresh();
        console.log('結束改變$location')
    });
    //這裡就算繫結了$routeChangeStart和$routeChangeSuccess,也不會被觸發,因為這裡沒有$route相關的服務.
});

*以上demo執行在我本地的http://localhost:801/這個wamp服務下.

 

1.新增base href標籤

新增了base href標籤後,頁面的基礎url就變為: http://localhost:801/$location/base/

2.hashbang和html5 history api處理瀏覽器相容

設定啟用html5的history api,設定bang識別符號為'!'

完成了1,2以後,在瀏覽器裡開啟 http://localhost:801/$location/base

在高階瀏覽器裡: 

在ie9模式:

這裡初始狀態的$location.url,一個有'/',一個沒有'/',不過這個不用介意.不影響實際的使用的.

需要重視的是,當連結發生過改變(比如我點選了連結1/some1?foo=bar),然後重新整理頁面,在ie9下,它依然可以載入到該頁面,但是在chrome下是不行的,所以,在使用了history api的瀏覽器裡,需要服務端配置.通常,你要把所有連結都轉給應用的入口點(比如index.html)。

3.$location雙向資料繫結

可以使用1.3提供的ng-model-options的getterSetter,給input繫結location方法.這樣,input輸入內容,就會呼叫$location.url(newLocation)設定url,當呼叫$location.url('...')的時候,input元素的value值也會被同步.需要注意,直接在位址列輸入url是沒用的,不過這無所謂,因為應用中也不會有人要跳轉的時候手動輸地址...

4.a連結改變url

點選前面兩個連結,url會改變,不重新整理頁面. 

 

完整程式碼:https://github.com/OOP-Code-Bunny/angular/tree/master/%24location 

參考文獻:http://www.ngnice.com/docs/guide/$location (中文版的是1.2的,不是最新的) https://docs.angularjs.org/guide/$location

 

相關文章