这是一个深度学习指南,会带你认识UI-Router、组件及其选项。如果你只是需要一个快速入门指南,请点击访问 API Reference。
State Manager(状态管理器)
UI-Router中,路由设置服务 $stateProvider 的使用跟AngularJS(即Angular 1.x版本)的 angular-router 十分类似,只不过它的关注点是 state(状态)。
- 一个state,指向整个UI和导航里某一个“地方”
- 一个state(通过 控制器/模版/视图 属性),描述了在这个“地方”的UI应该如何呈现和工作
- 通常情况下,一些state是有共同点,而要解决这些共性问题,主要采用状态继承(state hierarchy),也称为父/子状态(parent/child states)或嵌套状态(nested states)
The simplest form of state(简单示例)
以下是一个最简单的使用状态的例子(尤其是在module.config中配置)
<!-- index.html --> <body ng-controller="MainCtrl"> <section ui-view></section> </body> <script>
// app-states.js (你也可以换你喜欢的名字)
$stateProvider.state('contacts', { template: '<h1>My Contacts</h1>' })
Where does the template get inserted?(模版文件插到哪里去?)
当state被激活,对应的模版将被插入其父级 state 模板里的 ui-view 容器里,如果它是根状态(top-level state),就像上面例子中的“contacts”(没有设置父状态),那么它的父级 state 就是 index.html。
但是用“contacts”命名的state还没有被激活。所以让我们看看如何激活一个状态。
Activating a state(激活状态)
激活状态有三种方式:
- 调用 $state.go() 方法。高层次的便利方法。查看更多
- 点击一个带有“ui-sref”的 link (超链接)。查看更多
- 跳转到该状态对应的 url(网站地址)。查看更多
Templates(模版)
有几种方法可以配置 state 的模板。
上面的例子使用的就是最简单的一种配置模板的方法。
$stateProvider.state('contacts', { template: '<h1>My Contacts</h1>' })
除了直接在配置写模版 HTML 结构,你也可以加载一个 HTML 文档片段。(这是配置模板的常用方式)(译者:通过引用路径)
$stateProvider.state('contacts', { templateUrl: 'contacts.html' })
templateUrl也可以是一个函数,返回值是一个 URL 地址。此函数接收一个预置参数,$stateParams。(原文此处还有半句"which is not injected",不晓得啥意思)。
$stateProvider.state('contacts', { templateUrl: function ($stateParams){ return '/partials/contacts.' + $stateParams.filterBy + '.html'; } })
或者你可以用一个可注入的 template provider 函数,该函数能够访问局部变量,并且必须返回HTML模板,如:
$stateProvider.state('contacts', { templateProvider: function ($timeout, $stateParams) { return $timeout(function () { return '<h1>' + $stateParams.contactId + '</h1>' }, 100); } })
如果你希望在状态激活(状态里的UI渲染完毕)前显示一些默认内容,你可以像下面这样使用。只要状态被激活成功,这些默认内容将会被替换成 HTML 模版。
<body> <ui-view> <i>Some content will load here!</i> </ui-view> </body>
Controller(控制器)
你可以为你的模板绑定一个 controller 控制器。注意:如果模板没有定义,这个控制器将不会被实例化。
比如,你可以这样设置控制器:
$stateProvider.state('contacts', { template: '<h1>{{title}}</h1>', controller: function($scope){ $scope.title = 'My Contacts'; } })
如果你已经在 module 中定义了一个控制器,可以这样绑定:
$stateProvider.state('contacts', { template: ..., controller: 'ContactsCtrl' })
上面两种方式都可以用“controller as”语法:
$stateProvider.state('contacts', { template: '<h1>{{contact.title}}</h1>', controller: function(){ this.title = 'My Contacts'; }, controllerAs: 'contact' })
$stateProvider.state('contacts', { template: ..., controller: 'ContactsCtrl as contact' })
另外,如果你还有更高端的需求,可以使用 controllerProvider 来动态返回一个控制器函数或者字符串标识:
$stateProvider.state('contacts', { template: ..., controllerProvider: function($stateParams) { var ctrlName = $stateParams.type + "Controller"; return ctrlName; } })
控制器里面可以使用 $scope.$on() 方法来监听 state 变更的事件。
只有当对应的 scopes(译者:AngularJS里面的ViewModel)被创建,控制器才会被实例化。举例来说,用户操作,使得URL变更并命中某个state,$stateProvider首先会加载这个 state 对应的模板,然后再将控制器绑定到模版的 scope 上。
Resolve(预先处理)
你可以使用 resolve 为控制器提供自定义的内容和数据。resolve 是一个可选的(控制器)依赖项 map (译:映射表,下同),会被注入到控制器中。
只要 resolve 中有依赖项会返回 promise,控制器都会进行等待。只有当所有依赖项都被解决了后,控制器才会进行实例化,同时触发 $stateChangeSuccess事件。
这个 resolve 属性是一个 map 对象,包含若干 key/value (键值对):
- key——{string}:依赖项名称,在注入到控制器时作为形参。
- facotry——{string|function}:
- 如果是 string,代表一个服务名称
- 如果是 function,那么它的返回值将会注入到控制器中。如果返回值是一个 promise,那么它将会在控制器实例化之前被解决掉,而且返回值将会注入到控制器中去。
Examples(使用示例)
每一个在resolve里面的依赖项必须在控制器被实例化之前被解决掉。注意看每一个 resolve 依赖项是如何作为参数注入到控制器中的。
$stateProvider.state('myState', { resolve:{ // 示例:返回简单对象的函数 // 因为不是promise对象,所以会被马上解决掉 simpleObj: function(){ return {value: 'simple!'}; }, // 示例:返回promise对象的函数 // 这是一个典型的resolve依赖项使用场景. // 你需要先注入所需的服务,如 $http promiseObj: function($http){ // $http 请求某个URL返回一个promise return $http({method: 'GET', url: '/someUrl'}); }, // 示例:返回promise对象的函数. 如果你需要对结果进行处理, 使 用 then, 可以进行链式处理。 promiseObj2: function($http){ return $http({method: 'GET', url: '/someUrl'}) .then (function (data) { return doSomeStuffFirst(data); }); }, // 示例:使用 service 对应的字符串注入服务. // 注意,service也可以返回一个promise,使用原理如上 translations: "translations", // 示例:使用返回值为 promise 的服务 translations2: function(translations, $stateParams){ // 假设translations服务的getLang方法返回一个promise return translations.getLang($stateParams.lang); }, // 示例:使用返回值为 promise 的自定义服务 greeting: function($q, $timeout){ var deferred = $q.defer(); $timeout(function() { deferred.resolve('Hello!'); }, 1000); return deferred.promise; } }, // 该控制器函数将会在以上所有 resolve 依赖项都解决完才会进行实例化。然后这些对象将会被注入到控制器中。 controller: function($scope, simpleObj, promiseObj, promiseObj2, translations, translations2, greeting){ $scope.simple = simpleObj.value; // You can be sure that promiseObj is ready to use! $scope.items = promiseObj.data.items; $scope.items = promiseObj2.items; $scope.title = translations.getLang("english").title; $scope.title = translations2.title; $scope.greeting = greeting; } })
Attach Custom Data to State Objects(往state对象上挂载数据)
你可以挂载自定义数据到 state 对象上(推荐使用 data 属性以避免冲突)
// Example shows an object-based state and a string-based state var contacts = { name: 'contacts', templateUrl: 'contacts.html', data: { customData1: 5, customData2: "blue" } } $stateProvider .state(contacts) .state('contacts.list', { templateUrl: 'contacts.list.html', data: { customData1: 44, customData2: "red" } })
然后你可以(在控制器里)这样访问这些数据
function Ctrl($state){ console.log($state.current.data.customData1) // outputs 5; console.log($state.current.data.customData2) // outputs "blue"; }
onEnter and onExit callbacks(回调函数 onEnter 和 onExit)
当 state 被激活或被撤销激活时,将会分别调用 ' onEnter ' 和 ‘ onExit ’ 回调函数(如果在state配置里注册了的话)。这两个回调函数都可以访问所有 resolve 依赖项。
$stateProvider.state("contacts", { template: '<h1>{{title}}</h1>', resolve: { title: function () { return 'My Contacts' } }, controller: function($scope, title){ $scope.title = title; }, onEnter: function(title){ if(title){ ... do something ... } }, onExit: function(title){ if(title){ ... do something ... } } })
State Change Events(state 变化事件)
注意:在UI Router 1.0 版本中,state 变化事件已被弃用,使用 Transition Hooks 代替。
所有 state 变化时间都会在 $rootScope 层级被触发
- $stateChangeStart——在跳转开始时被触发
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams, options){ ... })
注意:
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams, options){ event.preventDefault(); // transitionTo() promise will be rejected with // a 'transition prevented' error })
- $stateNotFound——v0.3.0 在要跳转到的状态名称无法找到时触发。该事件触发后,将会被广播,从而给予所有对应的事件接收器一次机会对错误进行处理(usually by lazy-loading the unfound state)。其中,一个特殊对象, unfoundState 将会被传入到事件处理函数中。在下面的例子中,你将会看到它所拥有的三个属性。用 event.preventDefault() 可以中止跳转(transition To() promise 将会得到 rejected 结果,结果中包含 'transition aborted' 错误)。For a more in-depth example on lazy loading states, see How To: Lazy load states
// 在某处调用以下语句, 假设 lazy.state 状态没有被定义 $state.go("lazy.state", {a:1, b:2}, {inherit:false}); // 在另一处监听 $rootScope.$on('$stateNotFound', function(event, unfoundState, fromState, fromParams){ console.log(unfoundState.to); // "lazy.state" console.log(unfoundState.toParams); // {a:1, b:2} console.log(unfoundState.options); // {inherit:false} + default options })
- $stateChangeSuccess——当跳转完成时触发
-
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ ... })
$stateChangeError——当跳转时发生错误时触发。要特别注意的是,如果在resolve函数里面发生了错误(比如 js 错误,调用的服务不存在等),依照传统,这个错误将不会抛出。你必须监听 $stateChangeError 事件来捕获期间发生的所有错误。使用 event.preventDefault() 可以防止出错时 $urlRouter 自动切换回上一个有效地址(in case of a URL navigation)。
$rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error){ ... })
View Load Events(视图加载事件)
- $viewContentLoading——在视图(模板)开始加载时,并且是在DOM渲染完成之前触发。在 $rootScope 上广播(broadcast)该事件。
$rootScope.$on('$viewContentLoading', function(event, viewConfig){ // 能够访问所有 view 配置属性. // 也能够访问特殊属性——'targetView' // 这里可以这样调用这个特殊属性,viewConfig.targetView });
- $viewContentLoaded——在视图已经加载完毕,并且DOM已经渲染完成时触发。在 $scope 上触发(emit)该事件。
$scope.$on('$viewContentLoaded', function(event){ ... });