angular的uiRouter服務學習(1)


 angular有內置的路由服務$route:angular -- $route API翻譯

 使用$route可以幫助實現路由的切換,視圖的改變,但是這個內置的$route只包含了基本的功能,在很多場合下是不夠用的.所以,需要學習使用uiRouter.

 首先,在頁面中鏈入'angular-ui-router.min.js',然后在模塊中寫入依賴:

var myapp = angular.module('myApp',['ui.router']);

 然后就可以使用一個叫做$state的服務,使用$stateProvider來配置這個服務.

 $stateProvider和angualr內置的$routeProvider的用法類似,但是它是通過'狀態'來管理路由的.

  • 在整個應用的界面和導航中,狀態對應了頁面中的一個位置(也就是ui-view)
  • 狀態通過controller,template,view等屬性,描述了它對應位置的視圖展示和行為.
  • 狀態之間通常有一些共同點,把這些共同點從模型中分解出來的最好辦法就是通過狀態繼承. 比如父/子狀態(又名狀態嵌套)

 

 下面例舉一個最簡單的狀態:

<body ng-controller="MainCtrl">
 <section ui-view></section>
</body>
$stateProvider.state('contacts', { template: '<h1>My Contacts</h1>' })

 名為'contacts'的狀態,對應了ui中的'ui-view'指令元素.當contacts狀態被激活,ui-view元素就會被template填充.

 

 模板的插入位置:

 當一個狀態被激活,它的模板會被自動填充到父狀態模板里的ui-view元素里.如果這個狀態是個頂層狀態-比如上面例子中的'contacts'狀態,它沒有父狀態.那么,它的父狀態模板就是整個html.

 另外,ui-view元素可以擁有原始內容,用於當狀態還沒有被激活時展示,當狀態被激活后,原始內容會被替換掉:

<body>
    <ui-view>
        <i>Some content will load here!</i>
    </ui-view>
</body>

 現在,'contacts'狀態不會被激活,下面來看看如何激活它:

 激活一個狀態:

  • 調用 $state.go().具體用法以后再講.
  • 點擊一個帶有ui-sref屬性的a鏈接.ui-sref屬性值就是狀態值.具體用法以后再講.
  • 狀態里定義對應url,當頁面的url改變成對應狀態的url時,就激活這個狀態.具體用法以后再講.

 狀態的模板:

 有幾種方法可以定義狀態對應的視圖模板:

 1.定義template屬性,屬性值為字符串html:

$stateProvider.state('contacts', {
  template: '<h1>My Contacts</h1>'
})

 2.定義templateUrl屬性,屬性值一個函數,函數返回值為模板文件對應的url路徑:

    函數中可以注入$stateParams.$stateParams是url參數組成的鍵值對對象. 比如這里url里的name就是一個參數,那么,$stateParams就是{name:''}

$stateProvider.state('contacts',{
      url:'/contacts/:name',
      templateUrl: function($stateParams){
            return 'partials/contacts.' + $stateParams.name + '.html'
      }
})

 3.定義templateProvider屬性.屬性值是一個函數,函數的返回值為字符串html:

    函數中同樣可以注入$stateParams

$stateProvider.state('contacts',{
      url:'/contacts/:name',
      templateProvider: function($stateParams){
            return '<h1>'+$stateParams.name+'</h1>'
      }
})

 

 狀態的控制器:

 可以為每個視圖模板分配一個控制器. 注意:如果模板沒有被定義,那么控制器不會被實例化. 

 有以下幾種方式可以定義控制器:

 1.定義controller屬性,屬性值為模塊下定義好的控制器,

    在myapp模塊下定義了'contact'控制器,然后controller屬性就可以直接定義屬性值為'contact'

myapp.config(function($stateProvider){
    $stateProvider.state('contacts',{
        url:'/contacts/:name',
        controller:'contact',
        templateProvider: function($stateParams){
            return '<h1>'+'{{text}},'+$stateParams.name+'</h1>'
        }
    })
});
myapp.controller('contact',function($scope){
    $scope.text='hi'
});

 2.定義controller屬性,屬性值就是控制器函數

myapp.config(function($stateProvider){
    $stateProvider.state('contacts',{
        url:'/contacts/:name',
        controller:function($scope){
            $scope.text='hi'
        },
        templateProvider: function($stateParams){
            return '<h1>'+'{{text}},'+$stateParams.name+'</h1>'
        }
    })
});

 3.定義controller屬性,屬性值為模塊下定義好的控制器+'as con',並且定義controllerAs屬性為'con',然后在作用域中使用con

    這里的con只是我隨便取的名字...不是定死的...

myapp.config(function($stateProvider){
    $stateProvider.state('contacts',{
        url:'/contacts/:name',
        controller:'contact as con',
        templateProvider: function($stateParams){
            return '<h1>'+'{{con.text}},'+$stateParams.name+'</h1>'
        },
        controllerAs:'con'
    })
});
myapp.controller('contact',function(){
    this.text='hi'
});

 4.定義controller屬性,屬性值就是控制器函數,並且定義controllerAs屬性為'con',然后在作用域中使用con

    這里的con只是我隨便取的名字...不是定死的...

myapp.config(function($stateProvider){
    $stateProvider.state('contacts',{
        url:'/contacts/:name',
        controller:function(){
            this.text='hi'
        },
        templateProvider: function($stateParams){
            return '<h1>'+'{{con.text}},'+$stateParams.name+'</h1>'
        },
        controllerAs:'con'
    })
});

 控制器會根據需要,在對應的作用域被創建的時候實例化. 比如: 當url被改變到和狀態匹配的url時,$stateProvider會把對應的模板加載到視圖里,然后把控制器綁定到模板的作用域下.

 

 狀態的resolve:

 resolve屬性非常重要,它是一個map對象(也就是json對象),它為狀態的控制器提供了所需的依賴.這些依賴可以給狀態對應的控制器提供所需要的內容或數據.如果resolve屬性值中有promise對象,那么它會在控制器被實例化、$stateChangeSuccess事件觸發之前被解析並且轉換成值.

 resolve的屬性和屬性值應該是這樣的:

  • 屬性名: 這個名字將會被作為依賴,注入到controller里.
  • 屬性值: 字符串|函數     
    • 字符串: 當前模型下既有的服務名          
    • 函數: 函數的返回值會作為依賴,可以被注入到控制器中.如果函數的返回值是一個promise對象,那么它會在控制器被實例化、$stateChangeSuccess事件觸發之前被解析並且轉換成值.這個值會被作為依賴注入.  

 下面這段代碼介紹了常見的resolve的六種類型:

/*resolve*/
myapp.config(function($stateProvider){    
    $stateProvider.state('contacts',{
        url:'/contacts/:name',
        resolve:{
            //字符串格式:使用一個既有的服務
            first:'aService',
            //函數:函數的返回值就是將被注入的服務
            second:function(){
                return {data:'second的data'}
            },
            //函數:在函數中注入既有的服務
            third:function(anotherService,$stateParams){
                var data = anotherService.getName($stateParams.name);
                return {data:data}
            },
            //函數:返回一個promise對象,最終得到的將是resolve里的內容
            fourth:function($q,$timeout){
                var defer = $q.defer();
                $timeout(function(){
                    defer.resolve({data:'我是fourth的data'});
                    //注意,如果一個state的resolve里的某個promise被拒絕了,那這個state直接無法繼續下去了.
                    //defer.reject({data:'我是fourth的data'})
                },2000);
                return defer.promise;
            },
            //函數:返回$http返回的promise返回的promise,最終得到的是.then里面return的內容
            fifth:function($http){
                return $http({
                    method:'GET',
                    url:'/contacts/name'
                }).then(function(res){
                    return {data:res.data}
                },function(){

                })
            },
            //函數:返回$http返回的promise,最終得到的就是后台返回值.
            sixth:function($http){
                return $http({
                    method:'GET',
                    url:'/contacts/name'
                })
            }
        },
        templateUrl:function($stateParams){
            return 'partials/contacts.' + $stateParams.name + '.html'
        },
        controller:'ctrl'
    })
});
myapp.factory('aService',function(){
    return {
        getName:function(){
            alert('我是aService服務的getName方法')
        },
        data:'first的data'
    }
});
myapp.factory('anotherService',function(){
    return {
        getName:function(data){
            return data.toUpperCase()
        }
    }
});
myapp.controller('ctrl',function($scope,first,second,third,fourth,fifth,sixth){
    first.getName();
    $scope.data1 = first.data;
    $scope.data2 = second.data;
    $scope.data3 = third.data;
    $scope.data4 = fourth.data;
    $scope.data5 = fifth.data;
    $scope.data6 = sixth.data;
});

 1.first: 一個既有的服務名.

  注入'first'依賴就相當於注入了'aService'服務. 

{
        getName:function(){
            alert('我是aService服務的getName方法')
        },
        data:'first的data'
}

 2.second: 一個函數

  注入'second'依賴,得到的是這個函數的返回值

{data:'second的data'}

 3.third: 一個函數,函數中可以注入既有的服務

  其實這種情況和2一樣,只是說,函數里可以注入依賴

{data:'BUNNY'}

 4.fourth: 一個promise對象

  控制器會等到promise被解析以后再實例化,而注入的依賴,不是promise本身,而是promise被解析的值,需要注入的是,如果promise不是被resolve,而是被reject,那么js會被中斷,控制器不會被實例化.狀態切換也失敗了.

{data:'我是fourth的data'}

 5.fifth: 一個promise對象返回的promise對象

  其實這種情況和4一樣,promise.then返回的promise對象,會被.then()函數里的返回值解析.這適用於對返回值做一些處理后再返回.

  (這里后台返回 ['bunny','cat','dog'] )

{data:['bunny','cat','dog']}

 6.sixth: 返回一個$http返回的promise對象

  其實這種情況也和4一樣.這個promise會被返回值解析.所以最后得到的就是返回值了.

{data:['bunny','cat','dog']}

 

 給狀態對象添加自定義的數據:

 $stateProvider.state('name',{})中的.state第二個參數對象可以添加自定義的屬性和值,為了避免沖突,一般使用data屬性來為它添加自定義屬性.

 自定義的屬性可以通過$state.current.data來訪問到.

myapp.config(function($stateProvider){
    $stateProvider.state('contacts',{
        url:'/contacts/:name',
        templateUrl:function($stateParams){
            return 'partials/contacts.'+$stateParams.name+'.html'
        },
        data:{
            stateData1:111,
            stateData2:222
        },
        controller:function($scope,$state){
            $scope.data7 = $state.current.data.stateData1 + $state.current.data.stateData2
        }
    })
});

 

 狀態的onEnter和onExit回調:

 狀態的onEnter屬性和onExit屬性可以用來定義進入狀態和退出狀態所執行的回調:

 注意,回調函數中可以注入resolve里定義的依賴,比如下面的'title':

  $stateProvider.state('contacts',{
        url:'/contacts/:name',
        templateUrl:function($stateParams){
            return 'partials/contacts.'+$stateParams.name+'.html'
        },
        resolve:{
            title:function(){
                return 'contacts'
            }
        },
        onEnter: function(title){
            console.log('進入'+title+'狀態啦')
        },
        onExit: function(title){
            console.log('退出'+title+'狀態啦')
        }
 })

 

 狀態改變事件:

 這些事件都是在$rootScope上觸發的.

  •  $stateChangeStart: 當狀態開始改變時觸發, 接受5個參數:
    • event: 事件對象,使用event.preventDefault()可以阻止狀態發生改變. 
    • toState: toState是定義.state時傳入的第二個參數對象  
    • toParams: toParams就是$stateParams
    • fromState: fromState是上一個狀態(就是離開的狀態).state時傳入的第二個參數對象
    • fromParams: fromParams是上一個狀態(就是離開的狀態)的$stateParams   
  •  $stateChangeSuccess: 當狀態改變結束時觸發,可以接受5個參數,5個參數同'$stateChangeStart'的5個參數
  •  $stateNotFound: 當狀態沒有找到時觸發,接受4個參數: 
    • event: 事件對象,使用event.preventDefault()可以阻止js繼續執行,否則會報錯,卡住.
    • unfoundState: 一個對象,這個對象有三個屬性:
      • to: 前往的狀態名(也就是沒有找到的這個狀態名)
      • toParams: 前往的狀態的參數(在使用ui-sref或者$state.go()的時候可以傳入)
      • options: 使用$state.go()的時候傳入的第三個參數
    • fromState: 同上
    • fromParams: 同上 
  •  $stateChangeError: 當狀態改變失敗時觸發,需要注意,如果在狀態的resolve過程中遇到了問題(比如js錯誤,服務找不到,請求得不到響應等),這些錯誤不會像傳統的那樣被拋出,而是會在$stateChangeError里被捕獲.它可以接受6個參數
    • event: 事件對象
    • toState: 同上
    • toParams: 同上
    • fromState: 同上
    • fromParams: 同上
    • error: 一個包含了錯誤信息的對象 

 

<div>
  <a href="contacts/bunny">查看視圖</a>
  <a href="contacts/exit">離開</a>
  <a ui-sref="lalala({a:1,b:2})"></a>
  <section ui-view loading>點擊鏈接后內容會被加載在這里</section>
</div>

 

myapp.directive('loading',function($rootScope){
    return {
        restrict:'EA',
        link:function(scope,iEle,iAttrs,ctrl){
            console.log(scope===$rootScope);
            scope.$on('$stateChangeStart',function(event,toState,toParams,fromState,fromParams){
                console.log('狀態開始改變');
                /*toState是定義.state時傳入的第二個參數對象*/
                //console.log(toState);
                /*toParams就是$stateParams*/
                //console.log(toParams);
                /*fromState是上一個狀態.state時傳入的第二個參數對象*/
                //console.log(fromState);
                /*fromParams是上一個狀態的$stateParams*/
                //console.log(fromParams);
            });
            scope.$on('$stateChangeSuccess',function(event,toState,toParams,fromState,fromParams){
                console.log('狀態改變結束');
                /*參數全部同上*/
            });
            scope.$on('$stateNotFound',function(event,unfoundState,fromState,fromParams){
                console.log('沒有找到對應的狀態');
                /*unfoundState包含了三個屬性:*/
                /*1.to:前往的狀態名(也就是沒有找到的這個狀態名)
                * 2.toParams:前往的狀態的參數(在使用ui-sref或者$state.go()的時候可以傳入,這個例子里就是{a:1,b:2})
                * 3.options:使用$state.go()的時候傳入的第三個參數.
                * */
                /*最后兩個參數同上*/
                 console.log(unfoundState);
                //如果不寫這句,那么接下來就會報錯,卡住js進程了.
                event.preventDefault()
            });
            scope.$on('$stateChangeError',function(event, toState, toParams, fromState, fromParams, error){
                console.log('切換狀態出錯');
                /*error是一個包含了錯誤信息的對象*/
                console.log(error);
            });
            scope.$on('$viewContentLoading',function(event,viewConfig){
                console.log('視圖開始加載');
            });
            scope.$on('$viewContentLoaded',function(event){
                console.log('視圖渲染完畢')
            })
        }
    }
});

 *注意,官網里是說事件都在$rootScope上觸發,但是這里直接在指令元素的scope上也能觸發.

 

 視圖加載事件:

  •  $viewContentLoading: 當視圖開始渲染的時候,$rootScope傳播這個事件. 它接受2個參數
    • event: 事件對象
    • viewConfig: 包含一個屬性:targetView
  •  $viewContentLoaded: 當視圖渲染完畢的時候,視圖所在的scope傳播這個事件.
scope.$on('$viewContentLoading',function(event,viewConfig){
   console.log('視圖開始加載');
});
scope.$on('$viewContentLoaded',function(event){
   console.log('視圖渲染完畢')
})

 事件觸發執行順序:

 下面來理一下一個狀態被激活的過程是怎樣的:

 1. 觸發$stateChangeStart事件,如果使用event.preventDefault(),會阻止狀態改變.

   如果沒有找到對應狀態,會觸發$stateNotFound事件,然后中斷.

 2. 觸發$viewContentLoading事件.

 3. 如果在切換狀態的過程中出錯(比如resolve出錯),觸發$stateChangeError事件,無出錯跳過此步.

 4. 觸發上一個狀態(若有)的onExit回調事件

 5. 觸發當前狀態的onEnter回調事件

 6. 觸發$stateChangeSuccess事件

 7. 觸發$viewContentLoaded事件 

 

 完整代碼: https://github.com/OOP-Code-Bunny/angular/tree/master/uiRouter

 

 參考網站: https://github.com/angular-ui/ui-router/wiki

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM