2016-05-04 13:14 916人阅读 评论(0) 收藏 举报
本文章已收录于:

 

angular而言,它自然也有内置的路由模块:叫做ngRoute

于是,一个基于ngRoute开发的第三方路由模块,叫做ui.router,受到了大家的“追捧”。


首先,无论是使用哪种路由,作为框架额外的附加功能,它们都将以模块依赖的形式被引入,简而言之就是:在引入路由源文件之后,你的代码应该这样写(以ui.router为例):

angular.module("myApp", ["ui.router"]); // myApp为自定义模块,依赖第三方路由模块ui.router

在程序启动(bootstrap)的时候,加载依赖模块(如:ui.router),将所有挂载在该模块的服务(provider)指令(directive)过滤器(filter)等都进行注册,那么在后面的程序中便可以调用了。

ngRoute模块和ui.router模块各自都提供了哪些服务,哪些指令?

    ngRoute

    • $routeProvider(服务提供者) --------- 对应于下面的urlRouterProvider和stateProvider
    • $route(服务) --------- 对应于下面的urlRouter和state
    • $routeParams(服务) --------- 对应于下面的stateParams
    • ng-view(指令) --------- 对应于下面的ui-view
  1. ui.router
    • $urlRouterProvider(服务提供者) --------- 用来配置路由重定向
    • $urlRouter(服务)
    • $stateProvider(服务提供者) --------- 用来配置路由
    • $state(服务) --------- 用来显示当前路由状态信息,以及一些路由方法(如:跳转)
    • $stateParams(服务) --------- 用来存储路由匹配时的参数
    • ui-view(指令) --------- 路由模板渲染,对应的dom相关联
    • ui-sref(指令)
    • ...

注:服务提供者:用来提供服务实例和配置服务。)

ui.router和ngRoute大体的设计思路,对应的模块划分都是一致的(毕竟是同一个团队开发),不同的地方在于功能点的实现和增强


ngRoute弱在哪些方面,ui.router怎么弥补了这些方面?

  • 多视图
  • 嵌套视图

多视图:页面可以显示多个动态变化的不同区块。

比如:页面一个区块用来显示页面状态,另一个区块用来显示页面主内容,当路由切换时,页面状态跟着变化,对应的页面主内容也跟着变化。

ngRoute来做:

<div ng-view>区块1</div> <div ng-view>区块2</div>

$routeProvider .when('/', { template: 'hello world' });

  • 视图没有名字进行唯一标志,所以它们被同等的处理。
  • 路由配置只有一个模板,无法配置多个。

ui.router来做:

<div ui-view></div> <div ui-view="status"></div>

$stateProvider .state('home', { url: '/', views: { '': { template: 'hello world' }, 'status': { template: 'home page' } } });

  • 可以给视图命名,如:ui-view="status"。
  • 可以在路由配置中根据视图名字(如:status),配置不同的模板(其实还有controller等)。

注:视图名是一个字符串,不可以包含@(原因后面会说)。


嵌套视图:页面某个动态变化区块中,嵌套着另一个可以动态变化的区块。

比如:页面一个主区块显示主内容,主内容中的部分内容要求根据路由变化而变化,这时就需要另一个动态变化的区块嵌套在主区块中。

<div ng-view> I am parent <div ng-view>I am child</div> </div>

JavaScript,我们会在程序里这样写:

$routeProvider
.when('/', {
template: 'I am parent <div ng-view>I am child</div>'
});

ngRoute这样写,你会发现浏览器崩溃了,因为在ng-view指令link的过程中,代码会无限递归下去。

路由没有明确的父子层级关系!

ui.router是如何解决这一问题的?

$stateProvider
.state('parent', {
abstract: true,
url: '/',
template: 'I am parent <div ui-view></div>'
})
.state('parent.child', {
url: '',
template: 'I am child'
});
      巧妙地,通过

parent

parent.child

      来确定路由的

父子关系

    ,从而解决无限递归问题。

  1. 另外子路由的模板最终也将被插入到父路由模板的div[ui-view]中去,从而达到视图嵌套的效果。

路由,大致可以理解为:一个查找匹配的过程。

MVC(VM)而言,就是将hash值(#xxx)与一系列的路由规则进行查找匹配,匹配出一个符合条件的规则,然后根据这个规则,进行数据的获取,以及页面的渲染。

  • 第一步,学会如何创建路由规则?
  • 第二步,了解路由查找匹配原理?

首先,看一个简单的例子:
$stateProvider
.state('home', {
url: '/abc',
template: 'hello world'
});

$stateProvider.state(...)方法,创建了一个简单路由规则,通过参数,可以容易理解到:

    规则名:'home'

  1. 匹配的url:'/abc'
  2. 对应的模板:'hello world'

http://xxxx#/abc的时候,这个路由规则被匹配到,对应的模板会被填到某个div[ui-view]中。

这里需要深入的是:$stateProvider.state(...)方法,它做了些什么工作?

    首先,创建并存储一个state对象,里面包含着该路由规则的所有配置信息。

  1. 然后,调用$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?


其实这个问题跟大家一直说的:“ui.router是基于state(状态)的,而不是url”是同一个问题。

父子关系,每一个路由可以理解为一个state,

    当程序匹配到某一个子路由时,我们就认为这个子路由state被激活,同时,它对应的父路由state也将被激活。

  1. 我们还可以手动的激活某一个state,就像上面写的那样,$state.transitionTo(state, ...);,这样的话,它的父state会被激活(如果还没有激活的话),它的子state会被销毁(如果已经激活的话)。

$urlRouterProvider.when(...)方法,它做了什么呢?

rules集合进行的。


有了之前,路由的创建和注册,接下来,自然会想到路由是如何查找匹配的?
  • angular 在刚开始的digest时,‘rootScope会触发locationChangeSuccess‘事件(angular在每次浏览器hashchange的时候也会触发‘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();


遍历来查找匹配路由,然后跳转到对应的state吗?

那么ui.router对于这样的问题,会怎么进行优化呢?

答案是:可以的。

  • 会实例化一个对应的state对象,并存储起来(states集合里面)
  • 每一个state对象都有一个state.name进行唯一标识(如:'home')

ui-sref指令,来解决这个问题,比如这样:

<a ui-sref="home">通过ui-sref跳转到home state</a>

首先,ui-sref="home"指令会给对应的dom添加click事件,然后根据state.name,直接跳转到对应的state,代码像这样:

element.bind("click", function(e) {
// ..省略若干代码
var transition = $timeout(function() {
// 手动跳转到指定的state
$state.go(ref.state, params, options);
});
});

function update(evt) { var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl; // 手动调用$state.go(...)时,直接return避免下面的循环 if (ignoreUpdate) return true; // 省略下面的循环ruls代码 }

不建议直接使用href="#/xxx"来改变hash,然后跳转到对应state(虽然也是可以的),因为这样做会多了一步rules循环遍历,浪费性能,就像下面这样:

<a href="#/abc">通过href跳转到home state</a>

这里详细地介绍ui.router的参数配置和一些深层次用法。


官网demo无非就是最好的学习例子,里面涉及了大部分的知识点,所以接下来的代码讲解大部分都会是这里面的(建议下载到本地进行代码学习)。


之前就说到,在ui.router中,路由就有父与子的关系(多个父与子凑起来就有了,祖先和子孙的关系),从javascript的角度来说,其实就是路由对应的state对象之间存在着某种引用的关系。

数据结构的表示下contacts部分,大概是这样(原图):

parent字段维系了这样一个父与子的关系(粉红色的线)。

假设有一个父路由,如下:

$stateProvider
.state('contacts', {});

1.点标记法(推荐)

$stateProvider
.state('contacts.list', {});

状态名简单明了地来确定父子路由关系,如:状态名为'a.b.c'的路由,对应的父路由就是状态名为'a.b'路由。

parent属性

$stateProvider
.state({
name: 'list', // 状态名也可以直接在配置里指定
parent: 'contacts' // 父路由的状态名
});

$stateProvider .state({ name: 'list', // 状态名也可以直接在配置里指定 parent: { // parent也可以是一个父路由配置对象(指定路由的状态名即可) name: 'contacts' } });

parent直接指定父路由,可以是父路由的状态名(字符串),也可以是一个包含状态名的父路由配置(对象)。


父与子的关系,那么它们的注册顺序有要求嘛?

缓存子路由的信息,等待它的父路由注册完毕后,再进行子路由的注册。


当路由成功跳转到指定的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);
});

div[ui-view]在重新渲染的时候如何获取到对应视图模板的呢?

首先,我们得先看一下模板如何设置?

单视图的时候,我们会这样做:

$stateProvider
.state('contacts', {
abstract: true,
url: '/contacts',
templateUrl: 'app/contacts/contacts.html'
});

templateUrl指定模板路径即可。

多视图,就需要用到views字段,像这样:

$stateProvider
.state('contacts.detail', {
url: '/{contactId:[0-9]{1,4}}',
views: {
'' : {
templateUrl: 'app/contacts/contacts.detail.html',
},
'hint@': {
template: 'This is contacts.detail populating the "hint" ui-view'
},
'menuTip': {
templateProvider: ['$stateParams', function($stateParams) {
return '<hr><small class="muted">Contact ID: ' + $stateParams.contactId + '</small>';
}]
}
}
});
  • template:直接指定模板内容,另外也可以是函数返回模板内容
  • templateProvider:通过依赖注入的调用函数的方式返回模板内容

单视图和多视图模板的方式,其实最终它们在ui.router内部都会被统一格式化成的views的形式,且它们的key值会做特殊变化:

单视图会变成这样:

views: {
// 模板内容会被安插在根路由模板(index.html)的匿名视图下
'@': {
abstract: true,
url: '/contacts',
templateUrl: 'app/contacts/contacts.html'
}
}

多视图会变成这样:

views: {
// 模板内容会被安插在父路由(contacts)模板的匿名视图下
'@contacts': {
templateUrl: 'app/contacts/contacts.detail.html',
},
// 模板内容会被安插在根路由(index.html)模板的名为hint视图下
'hint@': {
template: 'This is contacts.detail populating the "hint" ui-view'
},
// 模板内容会被安插在父路由(contacts)模板的名为menuTip视图下
'menuTip@contacts': {
templateProvider: ['$stateParams', function($stateParams) {
return '<hr><small class="muted">Contact ID: ' + $stateParams.contactId + '</small>';
}]
}
}

key变化了,最明显的是出现了一个@符号,其实这样的key值是ui.router的一个设计,它的原型是:viewName + '@' + stateName,解释下:

viewName

    • 指的是ui-view="status"中的'status'
    • 也可以是''(空字符串),因为会有匿名的ui-view或者ui-view=""
  1. stateName
    • 默认情况下是父路由的state.name,因为子路由模板一般都安插在父路由的ui-view
    • 也可以是''(空字符串),表示最顶层rootState
    • 还可以是任意的祖先state.name

该模板将会被安插在名为stateName路由对应模板的viewName视图下(可以看看上面代码中的注释理解下)。

其实这也解释了之前我说的:“为什么state.name里面不能存在@符号”?因为@在这里被用于特殊含义了。

ui-view重新进行模板渲染时,是根据viewName + '@' + stateName来获取对应的视图模板内容(其实还有controller等)的。


父与子的关系,某种程度上就有了override(覆盖或者重写)可能。

$stateProvider .state('contacts.detail', { url: '/{contactId:[0-9]{1,4}}', views: { 'hint@': { template: 'This is contacts.detail populating the "hint" ui-view' } } }); $stateProvider .state('contacts.detail.item', { url: '/item/:itemId', views: { 'hint@': { template: ' This is contacts.detail.item overriding the "hint" ui-view' } } });

父与子的关系,且他们都对@hint定义了视图,那么当子路由被激活时(它的父路由也会被激活),我们应该选择哪个视图配置呢?

具体的,ui.router是如何实现这样的视图override的呢?

$state.current.locals这个变量一看究竟。


答案是:不会,只会从子路由对应的视图开始局部重新渲染。

有了模板之后,必然不可缺少controller向模板对应的作用域(scope)中填写数据,这样才可以渲染出动态数据。

$stateProvider .state('contacts', { abstract: true, url: '/contacts', templateUrl: 'app/contacts/contacts.html', resolve: { 'contacts': ['contacts', function( contacts){ return contacts.all(); }] }, controller: ['$scope', '$state', 'contacts', 'utils', function ($scope, $state, contacts, utils) { // 向作用域写数据 $scope.contacts = contacts; }] });

依赖注入的,它注入的对象有两种:

      已经注册的服务(service),如:

$state

utils

  1. 上面的reslove定义的解决项(这个后面来说),如:contacts
resolve在state配置参数中,是一个对象(key-value),每一个value都是一个可以依赖注入的函数,并且返回的是一个promise(当然也可以是值,resloved defer)。

resolve: { 'contacts': ['contacts', function( contacts){ return contacts.all(); }] }

resolve: { 'myResolve': ['contacts', function(contacts){ return contacts.all(); }] }

contacts.all()方法并返回了一个promise。

controller: ['$scope', '$state', 'myResolve', 'utils', function ($scope, $state, contacts, utils) { // 向作用域写数据 $scope.contacts = contacts; }]

  • 简化了controller的操作,将数据的获取放在resolve中进行,这在多个视图多个controller需要相同数据时,有一定的作用。
  • 只有当reslove中的promise全部resolved(即数据获取成功)后,才会触发'$stateChangeSuccess'切换路由,进而实例化controller,然后更新模板。

$stateProvider .state('parent', { url: '', resolve: { parent: ['$q', '$timeout', function ($q, $timeout) { var defer = $q.defer(); $timeout(function () { defer.resolve('parent'); }, 1000); return defer.promise; }] }, template: 'I am parent <div ui-view></div>' }) .state('parent.child', { url: '/child', resolve: { child: ['parent', function (parent) { // 调用父路由的解决项 return parent + ' and child'; }] }, controller: ['child', 'parent', function (child, parent) { // 调用自身的解决项,以及父路由的解决项 console.log(child, parent); }], template: 'I am child' });


html

  <div ui-view></div>
<div ui-view="status"></div>

$stateProvider .state('home', { url: '/home', resolve: { common: ['$q', '$timeout', function ($q, $timeout) { // 公共的resolve var defer = $q.defer(); $timeout(function () { defer.resolve('common data'); }, 1000); return defer.promise; }], }, views: { '': { resolve: { special: ['common', function (common) { // 访问state.resolve console.log(common); }] } }, 'status': { resolve: { common: function () { // 重写state.resolve return 'override common data' } }, controller: ['common', function (common) { // 访问视图自身的resolve console.log(common); }] } } });

  • 路由的controller除了可以依赖注入正常的service,也可以依赖注入resolve
  • 子路由的resolve可以依赖注入父路由的resolve,也可以重写父路由的resolve供controller调用
  • 路由可以有单独的state.resolve之外,还可以在views视图中单独配置resolve,视图resolve是可以依赖注入自身state.resolve甚至是父路由的state.resolve

转AngularJS路由插件的更多相关文章

  1. AngularJS 路由和模板实例及路由地址简化方法

    最近一同事在学习AngularJS,在路由与模板的学习过程中遇到了一些问题,于是今天给她写了个例子,顺便分享出来给那些正在学习AngularJS的小伙伴们. 话说这AngularJs 开发项目非常的爽 ...

  2. AngularJS 路由

    AngularJS 路由允许我们通过不同的 URL 访问不同的内容. 通过 AngularJS 可以实现多视图的单页Web应用(single page web application,SPA). 通常 ...

  3. AngularJS常用插件与指令收集

    angularjs 组件列表 bindonce UI-Router Angular Tree angular-ngSanitize模块-$sanitize服务详解 使用 AngularJS 开发一个大 ...

  4. Angularjs路由需要了解的那点事

    Angularjs路由需要了解的那点事 我们知道angularjs是特别适合单页面应用,为了通过单页面完成复杂的业务功能,势必需要能够从一个视图跳转到另外一个视图,也就是需要在单个页面里边加载不同的模 ...

  5. 【转】AngularJS路由和模板

    1. AngularJS路由介绍 AngularJS路由功能是一个纯前端的解决方案,与我们熟悉的后台路由不太一样.后台路由,通过不同的URL会路由到不同的控制器上(controller),再渲染(re ...

  6. AngularJS路由和模板

    前言 如果想开发一款类似gmail的web应用,我们怎么做呢? 以jQuery的思路,做响应式的架构设计时,我们要监听所有点击事件,通过事件函数触发我们加载数据,提交,弹框,验证等的功能:以 Angu ...

  7. AngularJS路由跳转

    AngularJS是一个javascript框架,通过AngularJS这个类库可以实现目前比较流行的单页面应用,AngularJS还具有双向数据绑定的特点,更加适应页面动态内容. 所谓单页面应用就是 ...

  8. AngularJS 路由精分

    AngularJS 路由机制是由ngRoute模块提供,它允许我们将视图分解成布局和模板视图,根据url变化动态的将模板视图加载到布局中,从而实现单页面应用的页面跳转功能. AngularJS 路由允 ...

  9. AngularJS进阶(二)AngularJS路由问题解决

    AngularJS路由问题解决 遇到了一个棘手的问题:点击优惠详情时总是跳转到药店详情页面中去.再加一层地址解决了,但是后来发现问题还是来了: Could not resolve 'yhDtlMain ...

随机推荐

  1. hexo博客零基础搭建系列(一)

    文章目录 其他搭建 1.简介 2.安装Node和Git 3.安装Hexo 4.Hexo的目录结构 5.我的版本 其他搭建 不好意思,下面的链接都是CSDN的链接,如果要在博客园看,请点我的分类查看.因 ...

  2. C++Primer第五版 3.5.1节练习

    练习 3.27:假设txt_size是一个无参数的函数,它的返回值是int.请回答下列哪个定义是非法的?为什么? Unsigned buf_size = 1024; (a) int ia[buf_si ...

  3. Zookeeeper的安装与集群搭建

    简介 Zookeeper下载 官网地址:点我直达 百度云盘:点我直达 踩坑录 官网下载一定要下载带bin的 要不然zookeeper起不起来,找不到加载类,原来从版本3.5.5开始,带有bin名称的包 ...

  4. django 建立安全索引

    上篇记录使用“CONCURRENTLY” 命令行执行不锁表索引,对于django, 如何执行呢?这里记录一种方法,修改django迁移文件. 在执行完迁移后,为了方便找到该迁移文件,可以采用指定命名迁 ...

  5. Springboot + 持久层框架JOOQ

    简介 官网链接 JOOQ是一套持久层框架,主要特点是: 逆向工程,自动根据数据库结构生成对应的类 流式的API,像写SQL一样 提供类型安全的SQL查询,JOOQ的主要优势,可以帮助我们在写SQL时就 ...

  6. elasticsearch为什么比mysql快

    mysql关系型数据库索引原理 数据库的索引是B+tree结构 主键是聚合索引 其他索引是非聚合索引,先从非聚合索引找,见下图 elasticsearch倒排索引原理 两者对比 对于倒排索引,要分两种 ...

  7. 一个低级shell简易学生信息管理系统-新增登陆注册功能

    还有bug 不修改了 小声bb一下 这玩意真的要控制版本 随手保存 本来有个超完整的版本 一开心被我rm - f 了 后续还出现了 更多的bug 仔细仔细 源码如下: record=stu.db if ...

  8. 团队项目——Alpha发布1

    这个作业属于哪个课程 https://edu.cnblogs.com/campus/xnsy/GeographicInformationScience/homework 这个作业要求在哪里 https ...

  9. 创建dynamics CRM client-side (十一) - 管理和关联所有的JS文件

    代码管理是一个无法避免的问题. 前面我也建议了大家每一个entity都应该拥有自身的js. 但是如果我们有一些global的function, 我们应该怎样去部署到每一个entity中呢? 我这里使用 ...

  10. JS-05-元素获取

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...