AngularUI Router
Angular-Ui 对于 AngularJS 开发者来说是一个非常重要的工具,其中 UI-Router 又是重中之重。UI-Router 插件提供了“嵌套作用域等规则”等有用的特性,对于复杂项目开发非常实用,最近在项目中开始使用 UI-Router ,于是决定好好把它学习一遍。
多视图
页面可以显示多个动态变化的不同区块
<div ui-view></div>
<div ui-view="status"></div>
$stateProvider
.state('home', {
url: '/',
views: {
'': {
template: 'hello world'
},
'status': {
template: 'home page'
}
}
});
嵌套视图
页面某个动态变化区块中,嵌套着另一个可以动态变化的区块
<div ng-view>
I am parent
<div ng-view>I am child</div>
</div>
$stateProvider
.state('parent', {
abstract: true,
url: '/',
template: 'I am parent <div ui-view></div>'
})
.state('parent.child', {
url: '',
template: 'I am child'
});
工作原理
就是将hash值(#xxx)与一系列的路由规则进行查找匹配,匹配出一个符合条件的规则,然后根据这个规则,进行数据的获取,以及页面的渲染。
路由的创建
创建
$stateProvider
.state('status', {
url: '/abc',
template: 'hello world'
});
首先,创建并存储一个state对象,里面包含着该路由规则的所有配置信息。
然后,调用$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
- 路由注册,调用了$urlRouterProvider.when(...)方法:
它创建了一个rule,并存储在rules集合里面,之后的,每次hash值变化,路由重新查找匹配都是通过遍历这个rules集合进行的。
路由的查找匹配
angular 在刚开始的$digest时,$rootScope会触发$locationChangeSuccess事件(angular在每次浏览器hash change的时候也会触发$locationChangeSuccess事件)
ui.router 监听了$locationChangeSuccess事件,于是开始通过遍历一系列rules,进行路由查找匹配
当匹配到路由后,就通过$state.transitionTo(state,...),跳转激活对应的state
最后,完成数据请求和模板的渲染
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();
在用ui.router在创建路由时:
会实例化一个对应的state对象,并存储起来(states集合里面)
每一个state对象都有一个state.name进行唯一标识(如:’home’)
<a ui-sref="home">通过ui-sref跳转到home state</a>
点击这个a标签时,会直接跳转到home state,而并不需要循环遍历rules,
然后跳转到对应的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
路由配置
状态注册顺序没有要求,可以在父路由存在之前,创建子路由(不过,不是很推荐),因为ui.router在遇到这种情况时,在内部会先缓存子路由的信息,等待它的父路由注册完毕后,再进行子路由的注册。
$stateProvider
.state('contacts.list', {});
$stateProvider
.state({
name: 'list', // 状态名也可以直接在配置里指定
parent: 'contacts' // 父路由的状态名
});
$stateProvider
.state({
name: 'list', // 状态名也可以直接在配置里指定
parent: { // parent也可以是一个父路由配置对象(指定路由的状态名即可)
name: 'contacts'
}
});
$stateProvider.state('parentState.childState', {
//template
template: '<h1>My Contacts</h1>',
templateUrl: 'contacts.html' ,
templateUrl: function ($stateParams){
return '/partials/contacts.' + stateParams.filterBy + '.html';
},
templateProvider: function ($timeout, $stateParams) {
return $timeout(function () {
return '<h1>' + $stateParams.contactId + '</h1>'
}, 100);
},
//controller
controller: function($scope){
$scope.title = 'My Contacts';
},
// 在模块中已经定义了一个控制器,只需要指定控制器的名称
controller: 'ContactsCtrl',
controllerProvider: function($stateParams) {
var ctrlName = $stateParams.type + "Controller";
return ctrlName;
},
//resolve
//### resolve
//简化了controller的操作,将数据的获取放在resolve中进行,这在多个视图多个controller需要相同数据时,有一定的作用。
//只有当reslove中的promise全部resolved(即数据获取成功)后,才会触发'$stateChangeSuccess'切换路由,进而实例化controller,然后更新模板。
resolve:{
// Example using function with simple return value.
// Since it's not a promise, it resolves immediately.
simpleObj: function(){
return {value: 'simple!'};
},
// Example using function with returned promise.
// 这是一种典型使用方式
// 请给函数注入任何想要的服务依赖,例如 $http
promiseObj: function($http){
// $http returns a promise for the url data
return $http({method: 'GET', url: '/someUrl'});
},
},
// 控制器将等待上面的解决项都被解决后才被实例化
controller: function($scope, simpleObj, promiseObj){
$scope.simple = simpleObj.value;
// 这里可以放心使用 promiseObj 中的对象
$scope.items = promiseObj.items;
}
})
模板渲染
当路由成功跳转到指定的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);
});
question:
每一个 div[ui-view] 在重新渲染的时候,如何获取到对应视图模板
单视图,多视图
单视图多视图,最终它们在ui.router内部都会被统一格式化成的views的形式
//单视图
$stateProvider
.state('contacts', {
abstract: true,
url: '/contacts',
templateUrl: 'app/contacts/contacts.html'
});
//格式化为多视图
$stateProvider
.state('contacts', {
views: {
// 模板内容会被安插在根路由模板(index.html)的匿名视图下
'@': {
abstract: true,
url: '/contacts',
templateUrl: 'app/contacts/contacts.html'
}
}
});
$stateProvider
.state('contacts', {
// 根状态,对应的父模板则是index.html
// 所以 contacts.html 将被加载到 index.html 中未命名的 ui-view 中
templateUrl: 'contacts.html'
})
.state('contacts.detail', {
views: {
// 嵌套状态,对应的父模板是 contacts.html
// 相对命名
// contacts.html 中 <div ui-view='detail'/> 将对应下面
"detail" : { },
// 相对命名
// 对应 contacts.html 中的未命名 ui-view <div ui-view/>
"" : { },
// 绝对命名
// 对应 contacts.detail.html 中 <div ui-view='info'/>
"info@contacts.detail" : { }
// 绝对命名
// 对应 contacts.html 中 <div ui-view='detail'/>
"detail@contacts" : { }
// 绝对命名
// 对应 contacts.html 中的未命名 ui-view <div ui-view/>
"@contacts" : { }
// 绝对命名
// 对应 index.html 中 <div ui-view='status'/>
"status@" : { }
// 绝对命名
// 对应 index.html 中 <div ui-view/>
"@" : { }
});
views Key
viewName + '@' + stateName
viewName
指的是ui-view="status"
中的’status’
也可以是''(空字符串),因为会有匿名的ui-view或者ui-view=""
stateName
默认情况下是父路由的state.name,因为子路由模板一般都安插在父路由的ui-view中
也可以是''(空字符串),表示最顶层rootState
还可以是任意的祖先state.name
该模板将会被安插在名为stateName路由对应模板的viewName视图下
参考链接:
http://bubkoo.com/2014/01/02/angular/ui-router/guide/index/