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. wpf 画五角星函数

    public void ABC() { var canvas = new Canvas(); Content = canvas; var points = new List<Point>( ...

  2. Django之models高级进阶技术详解

    目录 一.常用字段 1.AutoField 2.IntegerField 3.CharField 4.自定义及使用char 5.DateField 6.DateTimeField 二.字段合集 三.字 ...

  3. iOS-UITableView HeaderView随Cell一起移动

    我们在使用TableView的时候,有时会设置HeaderView,当我们滑动的时候,HeaderView不会随Cell滑出屏幕,而是会固定到导航栏下面.今天我们要实现HeaderView随滑动一起滑 ...

  4. java架构之路(多线程)大厂方式手写单例模式

    上期回顾: 上次博客我们说了我们的volatile关键字,我们知道volatile可以保证我们变量被修改马上刷回主存,并且可以有效的防止指令重排序,思想就是加了我们的内存屏障,再后面的多线程博客里还有 ...

  5. Golang最强大的访问控制框架casbin全解析

    Golang最强大的访问控制框架casbin全解析 Casbin是一个强大的.高效的开源访问控制框架,其权限管理机制支持多种访问控制模型.目前这个框架的生态已经发展的越来越好了.提供了各种语言的类库, ...

  6. 搞定SpringBoot多数据源(2):动态数据源

    目录 1. 引言 2. 动态数据源流程说明 3. 实现动态数据源 3.1 说明及数据源配置 3.1.1 包结构说明 3.1.2 数据库连接信息配置 3.1.3 数据源配置 3.2 动态数据源设置 3. ...

  7. 一文带你看清HTTP所有概念

    上一篇文章我们大致讲解了一下 HTTP 的基本特征和使用,大家反响很不错,那么本篇文章我们就来深究一下 HTTP 的特性.我们接着上篇文章没有说完的 HTTP 标头继续来介绍(此篇文章会介绍所有标头的 ...

  8. Miller-Rabin​素数测试算法

    \(Miller-Rabin\)​素数测试 用途 判断整数\(n\)是否是质数,在\(n\)较小的情况下,可以使用试除法,时间复杂度为\(O(\sqrt n)\).但当\(n\)的值较大的时候,朴素的 ...

  9. CSS中设置元素的圆角矩形

    圆角矩形介绍 在CSS中通过border-radius属性可以实现元素的圆角矩形. border-radius属性值一共有4个,左上.右上.左下.右下. border-radius属性值规则如下:第一 ...

  10. 「 从0到1学习微服务SpringCloud 」01 一起来学呀!

    有想学微服务的小伙伴没?一起来从0开始学习微服务SpringCloud,我会把学习成果总结下来,供大家参考学习,有兴趣可以一起来学!如有错误,望指正! Spring .SpringBoot.Sprin ...