去年的时候,在公司做过一个ui-router的分享,今天将它整理成文字,发出来。后面,也会陆陆续续将以前的技术分享发出来。
开篇之前,需要提前说明的是,此分享使用的Angular是1.x版本
的。
在Angular中,ngRoute
是Angular自带的路由模块,它是基于url来驱动视图
。
ui-router
是AngularUI独立的路由模块,它是基于状态来驱动视图
。相比于ngRoute,ui-router具有更强大的功能,主要体现在视图的嵌套方面。
安装
使用npm安装
npm install --save angular-ui-router
简单使用
在模板中定义添加
标签,添加ui-sref
跳转链接,然后在js中配置好路由(注册状态)。
index.html的代码如下:
HelloHome
helloWord.js中的代码如下:
//让app依赖ui.router
var myApp = angular.module('myApp', ['ui.router']);
myApp.config(function ($stateProvider, $urlRouterProvider) { var helloState = { name: 'hello', url: '/hello', template: '{{title}}
', controller: function ($scope) { $scope.title = "Hello world"; } }; var aboutState = { name: 'about', url: '/about', templateUrl: 'about.html' }; $stateProvider.state(helloState); $stateProvider.state(aboutState);});
上面,helloWord.js中注册路由时,使用name
、url
、template
等状态属性。那么,ui-router中有哪些状态属性呢?
- name: 状态的名字,例如hello
- url: 在浏览器里面的url部分,例如当hello状态激活时,浏览器里面将会变成/hello
- template: 模板,状态的视图
- templateUrl: 模板的url
- controller:控制器,它可以管理模板中的内容
在上面的index.html
代码中,我们使用ui-view
、ui-sref
和ui-sref-active
三个指令,这也是我们经常使用指令,它们代表意思是:
- ui-view: 某个状态激活时,会被加载到这个标签内部
- ui-sref:相当于href,是一个链接,点击会激活相应的状态
- ui-sref-active:当对应状态激活时,这个指令会给所在元素添加class
注册状态另外一种写
除了上面helloWorld.js
中的写法外,还有另外一种,这也是我常用的写法:
myApp.config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) { $stateProvider //对应前面的helloState .state("hello", { url: '/hello', template: '{{title}}
', controller: function ($scope) { $scope.title = "Hello world"; } }) //对应前面的aboutState .state("about", { url: '/about', templateUrl: 'about.html' });}]);
路由嵌套
路由嵌套包括了状态嵌套和视图嵌套,状态嵌套就是在注册状态时,建立状态树(父子状态关系);视图嵌套,就是在父状态的模板中需要有一个ui-view标签,它用来加载子状态的模板内容。
状态嵌套和视图嵌套是一起使用的,共同构成路由嵌套。如果没有建立状态的父子关系,跳转会报错;如果没有视图的嵌套关系,跳转时url变化,但是页面的内容不会变化,因为子状态的模板视图不知道加载到哪里。
嵌套实现的四种方式
- 使用.来进行嵌套,例如
.state('home.list', {})
- 使用
ui-router.stateHelper
去构建嵌套,是第三方实现的,请参考ui-router.stateHelper
- 使用parent属性,来指定父状态的名称,例如:
parent: 'home'
- 使用parent属性,来指定父状态对象,例如:
parent: home
"."嵌套
我最常使用的嵌套方式是使用"."
来嵌套,示例如下:
$stateProvider .state("home", { url: '/home', templateUrl: 'home.html' }) .state("home.list", { url: '/list', templateUrl: 'list.html', controller: function ($scope) { $scope.dataArray = ['first cell', 'second cell', 'third cell'] } })
注意: 上面的home.list
对应路径是#/home/list
,它是home
状态的url与子状态home.list
的url一起拼接起来
的。
使用parent
来嵌套
这个,我比较少使用,示例如下:
$stateProvider .state("home", { url: '/home', templateUrl: 'home.html' }) .state("detail", { url: '/detail', parent: 'home', template: '这是详情部分
' })
路由嵌套注意的地方
- 状态注册可以是任何顺序,子状态在父状态之前注册也没可以
- 父状态必须存在
- 当子状态激活时,它的父状态也是激活状态,例如home.list激活时,home也会激活
- 嵌套状态和视图时,子状态只会加载自己的模板到父状态的ui-view中。如果父状态模板中不存在ui-view时,激活子状态,url会发生变化,但是视图不会发生变化,因为没有加载子状态模板的ui-view。
路由嵌套时URL组成
有两种方式组成URL:拼接路径
和绝对路径
。
第一种:拼接路径(默认)
当前状态是父状态的url与当前状态的url拼接起来的,例如:
$stateProvider .state("home", { url: '/home', templateUrl: 'home.html' }) .state("home.list", { url: '/list', templateUrl: 'list.html', controller: function ($scope) { $scope.dataArray = ['first cell', 'second cell', 'third cell'] } })
第二种: 绝对路径(^)
使用^符号匹配绝对路径
,例如:
$stateProvider .state('contacts', { url: '/contacts', ...}).state('contacts.list', { url: '^/list', ...});
- contacts 状态匹配 /contacts
- contacts.list 状态匹配 /list
路由跳转
当我们使用路由时,还需要知道路由如何跳转的,而在ui-router中,跳转路由有下面几种方式:
第一种,通过$stage.go()来跳转
//跳转到home状态
$sate.go('home');
第二种,通过设置一个含有ui-sref指令的链接
Home
这种方式,点击Home就会跳转到home状态
。
第三种,通过url直接导航到对应页面
Hello
或者
window.location = '#/home';
注意: 这里是使用angular 1.5.8,如果是angular1.6以上
的,有!符号
,如window.location = '#!/home'。
跳转并刷新
除了简单跳转之外,有时,我们想跳转到某个状态下,并且刷新页面。这时,我们可以使用ui-sref
和ui-sref-opts
一起来实现,如下:
List
当然,我们也可以在js中,使用$stage.go()来实现,如下:
$state.go("home.list",{}, {reload: true});
相对跳转
实现相对跳转很简单,使用.
开头就行了。
在html中使用ui-sref
方式时,示例如下:
List
在js中,使用$stage.go()也可以实现,示例如下:
$state.go(".list");
Resolve
Resolve是一个可选的依赖,当设置它时,它内部的属性就会被注入到控制器中。它的用途是,为状态控制器提供定制化的数据。
示例如下:
.state("detail", { url: '/detail', parent: 'home', template: '这是详情部分
', resolve: { //resolve是一个 config: function () { return { name: 'homeConfig', content: 'Hell' } }, greeting: function($q, $timeout){ var deferred = $q.defer(); $timeout(function() { deferred.resolve('Hello!'); }, 2000); return deferred.promise; }, page: function ($http) { return $http.get('http://localhost'); } }, controller: function ($scope, config, greeting, page) { console.log(config); console.log(greeting); console.log(page); }})
注意:当Resolve中存在promise时,它会等待每个promise relove之后才实例化控制器,然后触发$stateChangeSuccess事件。还有,Resolve中的数据,在子状态也可以获取。
参数
路由传递参数有两种,一种将参数嵌入到url当中;另外一种,没有嵌入到url当中。
参数嵌入方式
第一种:参数嵌入在URL中
嵌入在URL中的参数也分成两种,一种是嵌入在URL路径中;另外一种是放在query部分(也就是?后面)
嵌入在URL路径中时
示例如下:
.state("about.detail", { url: '/detail/:name', template: '这是about的详情页面, 关于{{name}}
', controller: function ($scope, $stateParams) { $scope.name = $stateParams.name; }})
上面代码也可以使用括号,效果完全一样,如下:
.state("about.detail", { url: ‘/detail/{name}’, template: '这是about的详情页面, 关于{{name}}
', controller: function ($scope, $stateParams) { $scope.name = $stateParams.name; }})
注意:放在URL路径里面时,还可以使用正则,例如: url:'/detail/*'
参数放在query部分
在?后面指定参数,例如:url: "/contacts?myParam"
,它将匹配: "/contacts?myParam=value"
多个参数时,用'&'连接:url: "/contacts?myParam1&myParam2"
,它将匹配: "contacts?myParams1=value1&myParam2=value2"
。
第二种:参数不嵌入URL中
注册路由时,设置状态属性params
.state("home.test", { url: '/test', params: { name: null }, template: '这是测试页面
', controller: function ($scope, $stateParams) { console.log($stateParams); }})
传递参数
$state.go('about.detail', {name: 'default about'});
获取参数
.state("about.detail", { url: '/detail/:name', template: '这是about的详情页面, 关于{{name}}
', controller: function ($scope, $stateParams) { $scope.name = $stateParams.name; }});
$urlRouterProvider使用
$urlRouterProvider可以用来做重定向
、处理无效状态
和定制化url
。
我们经常使用的功能是重定向和处理无效请求,它分别通过when()
和otherwise()
来实现,而定制化url,则是通过rule()方法实现。
重定向
重定向使用的是when(),它可以传递字符串:
例如,我们项目中,当跳转到project状态时,会默认重定向到"所有项目",如下:
$urlRouterProvider.when("/project", "/project/list/所有");
它也可以传递参数:
例如,我们项目中,曾经就有重定向的情况:
$urlRouterProvider.when('/project/detail/:id/evaluate', ['$match', '$stateParams', function ($match, $stateParams) { return '/project/detail/' + $urlRouterProvider.$stateParams.id + '/evaluate/analyst'}]);
无效状态处理
无效状态处理,使用的是otherwise()
,它传递一个url或函数参数。
例如,在我们的一个项目中,跳转到无效的路由时,它默认会跳转到个人信息页面(”/account”)
$urlRouterProvider.otherwise("/account");
url定制化
url定制化,使用的是rule(),它需要传递一个函数来处理url。
例如,将所有url都转换成小写:
app.config(function ($urlRouterProvider) { $urlRouterProvider.rule(function ($injector, $location) { var path = $location.path(), normalized = path.toLowerCase(); if (path != normalized) { $location.replace().path(normalized); } });})
onEnter和onExit
状态还有两个可选的属性,那就是onEnter和onExit回调函数,当状态激活时,它会回调onEnter中的方法(在controller初始化之前);当状态从激活变成非激活时,它会回调onExit方法。
示例:
onEnter: function () { console.log('进入home页面');},onExit: function () { console.log('退出home页面')}
注意:这个回调函数,可以访问所有Resolved中的依赖,也就是说它也会等待所有Resolved中的promise处理完之后才执行。
状态变化事件
注意:状态变化事件在1.0之后被放弃了,被 transition hooks代替了。
- $stateChangeStart 状态变化开始
- $stateNotFound 未被发现
- $stateChangeSuccess 状态改变成功
- $stateChangeError 状态改变失败
使用,如下:
$rootScope.$on("$stateChangeSuccess", function () { console.log('状态改变成功');});
使用transition hooks代替时,如下:
myApp.run(function($rootScope) { $rootScope.$on('$stateChangeStart', function(evt, toState, toParams, fromState, fromParams) { console.log("$stateChangeStart " + fromState.name + JSON.stringify(fromParams) + " -> " + toState.name + JSON.stringify(toParams)); }); $rootScope.$on('$stateChangeSuccess', function() { console.log("$stateChangeSuccess " + fromState.name + JSON.stringify(fromParams) + " -> " + toState.name + JSON.stringify(toParams)); }); $rootScope.$on('$stateChangeError', function() { console.log("$stateChangeError " + fromState.name + JSON.stringify(fromParams) + " -> " + toState.name + JSON.stringify(toParams)); });});
视图加载事件
- $viewContentLoading 当视图开始加载时触发一次,在DOM渲染之前
- $viewContentLoaded 当视图加载完触发一次,在DOM渲染之后
示例如下:
$scope.$on("$viewContentLoading", function (event, viewConfig) {});
$scope.$on("$viewContentLoaded", function (event, viewConfig) {});
多个命名视图
一个模板中,还可以放多个命名的视图,如下:
多个视图可以显示各自的模板:
$stateProvider
.state('report',{
views: {
'filters': {
templateUrl: 'report-filters.html',
controller: function($scope){ ... controller stuff just for filters view ... }
},
'tabledata': {
templateUrl: 'report-table.html',
controller: function($scope){ ... controller stuff just for tabledata view ... }
},
'graph': {
templateUrl: 'report-graph.html',
controller: function($scope){ ... controller stuff just for graph view ... }
}
}
})