1. 程式人生 > >AngularJS路由之ui-router(二)

AngularJS路由之ui-router(二)

模組建立時新增模組依賴“ui.router”

var app = angular.module('myApp', ['ui.router']);
後就可以使用一個叫做$state的服務,使用$stateProvider來配置這個服務.
$stateProvider和angualr內建的$routeProvider的用法類似,但是它是通過'狀態'來管理路由的.
  • 在整個應用的介面和導航中,狀態對應了頁面中的一個位置(也就是ui-view)
  • 狀態通過controller,template,view等屬性,描述了它對應位置的檢視展示和行為.
  • 狀態之間通常有一些共同點,把這些共同點從模型中分解出來的最好辦法就是通過狀態繼承. 比如父/子狀態(又名狀態巢狀)
啟用一個狀態:
呼叫 $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'

app.config(function ($stateProvider, $urlRouterProvider) {
    //註冊路由,使用控制器操作(使用外部控制器)
    //輕鬆實現多個檢視使用一個控制器
    $stateProvider.state('contacts', {
        url: '/contacts/:name',
        controller: 'contact',
        templateProvider: function ($stateParams) {
            return '<h1>{{text}},' + $stateParams.name + '</h1>';
        }
    });
});
app.controller('contact', function ($scope) {
    $scope.text = "hi";
});
2.controller屬性,屬性值就是控制器桉樹
app.config(function ($stateProvider, $urlRouterProvider) {
    //註冊路由,使用控制器操作(註冊路由時,指定控制器)
    //輕鬆實現多個檢視使用一個控制器
    $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’,並且定義controllerAd屬性為'con',然後在作用域中使用con作為controller物件
app.config(function ($stateProvider, $urlRouterProvider) {
    //使用as 重新命名控制器
    $stateProvider.state('contacts', {
        url: '/contacts/:name',
        controller: 'contact as con',
        templateProvider: function ($stateParams) {
            return '<h1>{{con.text}},' + $stateParams.name + '</h1>';
        },
        controllerAs: 'con'
    });
});
app.controller('contact', function () {
    this.text = "hi";
});
app.config(function ($stateProvider, $urlRouterProvider) {
    //使用as 重新命名控制器
    $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的六種型別:
var app = angular.module('myApp', ['ui.router']);
app.config(function ($stateProvider, $urlRouterProvider) {
    //向controller注入服務
    $stateProvider.state('contacts', {
        url: '/contacts/:name',
        templateUrl: function ($stateParams) {
            console.info($stateParams);
            return '/template/' + $stateParams.name + '.html'
        },
        controller: 'ctrl',
        resolve: {
            //字串格式,使用一個既有的服務
            first: 'aService',
            //函式:函式的返回值就是將被注入的服務
            send: function () {
                return { data: 'second的data ' }
            },
            //函式:在函式中注入既有的服務
            third: function (anotherService, $stateParams) {
                var data = anotherService.getName($stateParams.name);
                return { data: data }
            },
            //函式:返回一個promise物件,最終得到的殭屍resolve裡的內容
            fourth: function ($q, $timeout) {
                //限制程式等待,當前view會出現顯示阻塞
                var defer = $q.defer();
                $timeout(function () {
                    defer.resolve({ data: '我是fourth的data' });
                    //注意,如果一個state的resolve裡的某個promise被拒絕了,那這個state直接無法繼續下去了.
                    //defer.reject({data:'我是fourth的data'})
                }, 10000);
                return defer.promise;
            },
            //函式:返回$http返回的promise返回的promise,最終得到的事.then裡面return的內容
            fifth: function ($http) {
                return $http({
                    method: 'GET',
                    url: '/contents/getName'
                }).then(function (res) {
                    return { data: res.data }
                }, function () {
                    console.error('請求失敗');
                });
            },
            //函式:返回$http返回的promise,最終得到的就是後臺返回值
            sixth: function ($http) {
                return $http({
                    method: 'GET',
                    url: '/Contents/name'
                });
            }
        }
    });
});
//註冊服務
app.factory('aService', function () {
    return {
        data: 'first的data',
        getName: function () {
            console.info('aService服務下的getName方法觸發');
        }
    }
});
app.factory('anotherService', function () {
    return {
        getName: function (data) {
            return data.toUpperCase();
        }
    }
});
//註冊controller
app.controller('ctrl', function ($scope, first, send, third, fourth, fifth, sixth) {
    first.getName();
    console.info(first);
    $scope.data1 = first;
    $scope.data2 = send;
    $scope.data3 = third;
    $scope.data4 = fourth;
    $scope.data5 = fifth.data;
    $scope.data6 = sixth.data;
});
給狀態新增自定義資料:

$stateProvider.state('name',{})中的.state第二個引數物件可以新增自定義的屬性和值,為了避免衝突,一般使用data屬性來為它新增自定義屬性.
自定義的屬性可以通過$state.current.data來訪問到.

app.config(function ($stateProvider, $urlRouterProvider) {
    //1.使用路由狀態傳遞引數
    //2.路由狀態的onEnter和onExit回撥處理
    $stateProvider.state('contacts', {
        url: '/contacts/:name',
        templateUrl: function ($stateParams) {
            return '/template/' + $stateParams.name + '.html'
        },
        data: {
            data: 1234
        },
        controller: function ($scope, $state) {
            //獲取路由狀態的引數
            // console.info($state.current.data);
            $scope.data1 = $state.current.data;
        },
        resolve: {
            title: function () {
                return 'contacts';
            }
        },
        onEnter: function (title) {
            console.log('進入' + title + '狀態');
        },
        onExit: function (title) {
            console.log('推出' + title + '狀態');
        }
    });
});
狀態的onEnter和onExit回撥:
狀態的onEnter屬性和onExit屬性可以用來定義進入狀態和退出狀態所執行的回撥:
注意,回撥函式中可以注入resolve裡定義的依賴,比如下面的'title':
狀態改變事件:

這些事件都是在$rootScope上觸發的.測試證明在$scope中繫結也是管用的
$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: 一個包含了錯誤資訊的物件

檢視載入事件:
$viewContentLoading: 當檢視開始渲染的時候,$rootScope傳播這個事件. 它接受2個引數
event: 事件物件
viewConfig: 包含一個屬性:targetView
$viewContentLoaded: 當檢視渲染完畢的時候,檢視所在的scope傳播這個事件.

事件觸發執行順序:
下面來理一下一個狀態被啟用的過程是怎樣的:
1. 觸發$stateChangeStart事件,如果使用event.preventDefault(),會阻止狀態改變.
如果沒有找到對應狀態,會觸發$stateNotFound事件,然後中斷.
2. 觸發$viewContentLoading事件.
3. 如果在切換狀態的過程中出錯(比如resolve出錯),觸發$stateChangeError事件,無出錯跳過此步.
4. 觸發上一個狀態(若有)的onExit回撥事件
5. 觸發當前狀態的onEnter回撥事件
6. 觸發$stateChangeSuccess事件
7. 觸發$viewContentLoaded事件 

簡單例項:

<div class="container" ng-app="myApp">
    <h3>AngularJS Home Page Ui-router Test</h3>
    <ul class="nav nav-pills">
        <li role="presentation" class="active">
            <a href="#/contacts/index">首頁</a>
        </li>
        <li role="presentation">
            <a href="#/contacts/list">列表頁</a>
        </li>
        <li role="presentation">
            <a href="#/contacts/stop">被拒絕</a>
        </li>
        <li role="presentation">
            <a ui-sref="lalala({a:1,b:2})">無</a>
        </li>
    </ul>
    <div data-ui-view="" loading>點選連線後內容被載入咋愛這裡</div>
</div>
js:
var app = angular.module('myApp', ['ui.router']);
app.config(function ($stateProvider, $urlRouterProvider) {
    //1.使用路由狀態傳遞引數
    //2.路由狀態的onEnter和onExit回撥處理
    $stateProvider.state('contacts', {
        url: '/contacts/:name',
        templateUrl: function ($stateParams) {
            return '/template/' + $stateParams.name + '.html'
        },
        data: {
            data: 1234
        },
        controller: function ($scope, $state) {
            //獲取路由狀態的引數
            // console.info($state.current.data);
            $scope.data1 = $state.current.data;
        }
    });
});
//註冊指令
app.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.info(toState);
                /*toParams就是$stateParams*/
                console.info(toParams);
                /*fromState是上一個狀態.state時傳入的第二個引數物件*/
                console.info(fromState);
                /*fromParams就是上一個狀態的$stateParams*/
                console.log(fromParams);
                if (toParams.name == 'stop') {
                    alert('阻止頁面');
                    event.preventDefault();
                }
            });
            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.info(error);
            });
            //檢視事件
            scope.$on('$viewContentLoading', function (event, viewConfig) {
                console.log('檢視開始載入');
            });
            scope.$on('$viewContentLoaded', function (event) {
                console.log('檢視渲染完畢');
            });
        }
    }
});