路由机制运能够实现多视图的单页Web应用(single page web application,SPA)。html
单页应用在使用期间不会从新加载页面,全部的数据交互由ajax完成。前端
非单页应用请求不一样url,对SEO很不友善,并且刷新或发送url没法保留浏览进度。html5
而单页应用前端拥有监管url的权利,经过监听页面地址变化来调用前端MVC模块,渲染不一样页面。固然,前端这时也拥有了修改URL的能力,只须要管理好前端视图与URL。在肯定了须要保持的页面后,先后端围绕页面各自负责视图的渲染。web
这一种的状况是url彻底不变,即你的页面怎么改变,怎么跳转url都不会改变。
这种状况的原理 就是纯ajax拿到页面模板后替换原页面中的元素,重现渲染页面。然而ajax的一个致命缺点就是致使浏览器后退按钮失效。用户体验很很差。ajax
这种类型的优势就是刷新页面,页面也不会丢。关于hash值的介绍连接
经过监听 hash(#)的变化来执行js代码 从而实现 页面的改变 bootstrap
核心代码:后端
window.addEventListener('hashchange',function(){ //code })
就是经过这个原理 只要#改变了 就能触发这个事件,这也是不少单页面网站的url中都也 (#)的缘由api
这种类型是经过html5的最新history api来实现的。
经过pushState()记录操做历史,监听window.onpopstate事件来进行视图切换,能正常的回退前进。
这里是关于 history api的链接promise
关于这三种方法的demo日后会给你们提供完善,你们能够提醒我。浏览器
AngularJS 路由就是经过经过hash和h5 history两种方式实现的,容许咱们经过不一样的 URL 访问不一样的内容。
一般咱们的URL形式为 http://runoob.com/first/page,但在单页Web应用中 AngularJS 经过 # + 标记 实现,例如:
http://cds.com/#/firstPage http://cds.com/#/secondPage
当咱们点击以上的任意一个连接时,向服务端请的地址都是同样的 (http://runoob.com/)。 由于 # 号以后的内容在向服务端请求时会被浏览器忽略掉。 因此咱们就须要在客户端实现 # 号后面内容的功能实现。
AngularJS 路由经过 # + 标记 帮助咱们区分不一样的逻辑页面并将不一样的页面(View)绑定到对应的控制器(controller)上。
下面咱们介绍一下Angular的路由实现。
ngRouter是angular的一个单独模块。
咱们看看他是怎么实现的。
一、加载实现路由的 js 文件:angular-route.js。
二、包含了 ngRoute 模块做为主应用模块的依赖模块。(为何这么写,下文介绍)
angular.module('routingDemoApp',['ngRoute'])
三、使用 ngView 指令。
<div ng-view></div>
该 div 内的 HTML 内容会根据路由的变化而变化。
四、配置 $routeProvider,AngularJS $routeProvider 用来定义路由规则。
angular.module('MyApp', ['ngRoute']).config(['$routeProvider', function($routeProvider){ //$routeProvider 为咱们提供了 when(path,object) & otherwise(object) 函数按顺序定义全部路由,函数包含两个参数:第一个参数是 URL 或者 URL 正则规则。第二个参数是路由配置对象。 $routeProvider.when(url, { template: string,//在 ng-view 中插入简单的 HTML 内容 templateUrl: string,//在 ng-view 中插入 HTML 模板文件 controller: string, function 或 array,//在当前模板上执行的controller函数,生成新的scope。 controllerAs: string,//为controller指定别名。 redirectTo: string, function,//重定向的地址。 resolve: object<key, function>//指定当前controller所依赖的其余模块。 }); $routeProvider.otherwise({redirectTo:'/'})//除配置路由外的其余路径指向 }]);
在使用ngRouter时每每不能进行视图嵌套,并且功能有限,不能知足开发需求。由此咱们就须要另外一个第三方路由模块,叫作 ui.router ,固然它是基于ngRouter开发的。
ui.Router提供了一种很好的机制,能够实现深层次嵌套。
一、在引入ui.route路由源文件
二、加载依赖模块
angular.module("myApp", ["ui.router"]); // myApp为自定义模块,依赖第三方路由模块ui.router
在程序启动(bootstrap)的时候,加载依赖模块(如:ui.router),将全部 挂载 在该模块的 服务(provider) , 指令(directive) , 过滤器(filter) 等都进行注册 ,那么在后面的程序中即可以调用了。
angular.module("myApp", ["ui.router","myFilter","myDirective","myService"]); //实际中只要依赖首页须要用的模块就能够。
三、定义视图
<div ui-view></div>//视图展现 <a ui-sref='dash'>dash页</a>//定义导航
在视图模板中还能够包含本身的 ui-view ,这就是咱们能够支持嵌套路径的缘由。
<div ui-view> <div ui-view></div> </div>
四、配置 $stateProvider,在ui.router经过$stateProvider来定义路由规则
angular.module("MyApp").config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) { //$stateProvider为咱们提供了state(pathName,object) & otherwise(object) 函数按顺序定义全部路由,函数包含两个参数:第一个参数是 URL名称(视图中导航名称)。第二个参数是路由配置对象。 $urlRouterProvider.otherwise("/dash.html"); //定义默认跳转和除配置以外的定向路由 $stateProvider .state('dash'(string), {//导航用的名字,如<a ui-sref="dash">dash</a>里的login url: "/dash.html"(string),//访问路径,可视的路径 template: string,//在 ui-view 中插入简单的 HTML 内容 templateUrl: "views/dash.html"(string),//在 ui-view 中插入 HTML 模板文件(静态文件路径) data: {pageTitle: "系统主页"}(object<key,val>能够多层嵌套),//data数据不会注入到控制器,能够从父状态向子状态传递数据 controller: "DashController",//在当前模板上执行的controller函数,生成新的scope。 params:object,//params 选项是参数名称(下文有栗子)。当状态激活的时候,应用会使用这些参数填充 $stateParams 服务。 resolve: { deps: ['$ocLazyLoad', function ($ocLazyLoad) {//'$ocLazyLoad'是controller的依赖模块,返回执行方法。AngularJs 经过 ocLazyLoad 实现动态(懒)加载模块和依赖。具体的有时间再扩展。留个连接!!! return $ocLazyLoad.load({ name: 'MetronicApp', insertBefore: '#ng_load_plugins_before', files: [ 'app/controllers/DashController.js', ] }); }] }//在下文介绍 }) }]);
resolve 功能
使用 resolve 功能,咱们能够准备一组用来注入到控制器中的依赖对象,在 uiRoute 中,resolve 能够在路由实际渲染以前解决掉 promise。
resolve 选项提供一个对象,对象中的 key 就是准备注入 controller 的依赖名称,值则是建立对象的工厂。
若是是一个字符串,就试图用这个串来匹配当前已经注册的服务名称,
若是是一个函数,执行这个函数,返回的值就是依赖。
若是函数返回一个 promise,在控制器被实例化以前,将会被 resolved,返回的值被注入到 controller中。
先说说为何要视图嵌套,页面一个主区块显示主内容,主内容中的部份内容要求根据路由变化而变化,这时就须要另外一个动态变化的区块嵌套在主区块中。
若是用ngRoute 实现视图嵌套,你会发现是不可能的,由于在ng-view指令link的过程当中,代码会无限递归下去。形成这种现象的最根本缘由:路由没有明确的父子层级关系。
而uiRouter很好的解决了这一问题。
惯例先上代码:
$stateProvider .state('parent', { abstract: true,//能够经过它解决依赖问题,或者特定数据处理,或者简单地一样的 url 来嵌套多个路由,例如,全部路由都在 /parent下面。 url: '/parent',//相对路径=>…./index.html#/parent template: 'I am parent <div ui-view></div>'//固然你也能够写在templateUrl中 }) .state('parent.child', { url: '/child',//相对路径=>…./index.html#/parent/child url:'^/child',//绝对路径=>…./index.html#/child 注意写法区别 template: 'I am child' }); <!--herf --> <a ui-sref="parent">点我显示父view内容</a> <a ui-sref="parent.child">点我显示父view与子view内容</a> <!--view --> <div ui-view></div> <!-- 父View -->
巧妙地,经过 parent 与 parent.child 来肯定路由的 父子关系 ,从而解决无限递归问题。另外子路由的模板最终也将被插入到父路由模板的div[ui-view]中去,从而达到视图嵌套的效果。
多视图:页面能够显示多个动态变化的不一样区块。
缘由在于,在ui.router中:能够给视图命名(字符串),如:ui-view="status"。能够在路由配置中根据视图名字(如:status),配置不一样的模板(其实还有controller等)。
放一段代码
<div ng-app="myApp" > <a ui-sref="index">点我显示index内容</a> <div ui-view="header"></div> <div ui-view="nav"></div> <div ui-view="body"></div> </div> var app = angular.module('myApp', ['ui.router']); app.config(["$stateProvider", function ($stateProvider) { $stateProvider .state("index", { url: '/index', views:{ 'header':{template:"<div>头部内容</div>"}, 'nav':{template:"<div>菜单内容</div>"}, 'body':{template:"<div>展现内容</div>"} } }) }]);
待完善(项目无涉及)(控制起来也很麻烦,处理很差都是坑,若是要用到再完善吧。)
@的做用 是用来绝对定位view,即说明该ui-view属于哪一个模板。
一样放一段代码
<div ng-app="myApp" > <a ui-sref="index">show index</a> <a ui-sref="index.content1">content111111</a> <a ui-sref="index.content2">content222222</a> <div ui-view="index"><div> </div> var app = angular.module('myApp', ['ui.router']); app.config(["$stateProvider", function ($stateProvider) { $stateProvider .state("index", { url: '/index', views:{ 'index':{template:"<div><div ui-view='header'></div> <div ui-view='nav'></div> <div ui-view='body'></div> </div>"}, //这里必需要绝对定位 'header@index':{template:"<div>头部内容header</div>"}, 'nav@index':{template:"<div>菜单内容nav</div>"}, 'body@index':{template:"<div>展现内容contents</div>"} } }) //绝对定位 .state("index.content1", { url: '/content1', views:{ 'body@index':{template:"<div>content11111111111111111</div>"} //'body@index'表时名为body的view使用index模板 } }) //相对定位:该状态的里的名为body的ui-view为相对路径下的(即没有说明具体是哪一个模板下的) .state("index.content2", { url: '/content2', views:{ 'body':{template:"<div>content2222222222222222222</div>"}// } }) }]);
待完善(项目无涉及)(其实也不难理解,只是这个时间段任务重了,这里有时间再了解完善啊)。
Angular应用经过经过$stateParams服务获取参数他有两种方式:(相对温习一下路径穿参方式,$location.search())
1.url: '/index/:id'
2.url: '/index/{id}'
注意这里只是两种不一样写法
angular.module("MetronicApp").config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) { $stateProvider .state('dash', { url: "/dash.html/:orderId/:projectId", //或者注意是或者(这种须要直接定义好,或由页面传值) url:"/dash.html/{orderId}", //若是应用访问 /dash.html/42,那么,$stateParameter.orderId 就成为 42, 实际上, $stateParams 的值将为 { orderId: 42 } //或者(这种是在路由中定义好参数) url:"/dash.html", params: { orderId: { value: 42} } templateUrl: "/dash.html", controller: function ($stateParams,$scope) { //能够注入$stateParams服务来获取你所传递的服务 $scope.projectId=$stateParams.projectId; $scope.orderId=$stateParams.orderId; //42 } }) }]); //而后你们在看页面中的写法 也是两种方式建议用第二种() <a href="#/dash/42" >href传参数</a> <a ui-sref="dash({'orderId':42,'projectId':256})">ui-sref传参数</a>
再来扩展一下,在控制器中的页面跳转,而且传参
angular.module("MetronicApp",['ui.router']); angular.module("MetronicApp").controller('MyCtrl', function($scope, $state,$location) { $scope.pathChange = function() { $state.go('dash',{name: 42}); //第一个参数是定义的urlName,第二个为参数 }; $scope.pathChange = function() { $location.path('/sixth/detail/42'); //$location服务中有介绍 }; });
可能你们会有疑问,咱们项目的路径和这个示例不同,这里的参数是angular是控制路径跳转的参数目的在与定向视图或者视图之间丶控制器之间的通讯,不是从后台请求的,这个要搞清楚。
和以前说的$rootScope通讯效果是同样的。
Angular 路由状态发生改变时能够经过'$stateChangeStart'、'$stateChangeSuccess'、'$stateChangeError'监听,经过注入'$location'实现状态的管理。
代码示例以下:
function run($ionicPlatform, $location, Service, $rootScope, $stateParams) { //路由监听事件 $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) { console.log(event); //该事件的基本信息 console.log(toState); //咱们能够获得当前路由的信息,好比路由名称,url,视图的控制器,模板路径等等 console.log(toParams); //咱们能够获得当前路由的参数 console.log(fromState); //咱们能够获得上一个路由的信息,好比路由名称,url,视图的控制器,模板路径等等 console.log(fromParams); //咱们能够获得上一个路由的参数 if (toState.name == "dash") { //获取参数以后能够调请求判断须要渲染什么页面,渲染不一样的页面经过 $location 实现 if (toParams.id == 10) { //$location.path();//获取路由地址 // $location.path('/validation').replace(); // event.preventDefault()能够阻止模板解析 } } }) // stateChangeSuccess 当模板解析完成后触发 $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) { //code }) // $stateChangeError 当模板解析过程当中发生错误时触发 $rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error) { //code }) }
在页面渲染中 可经过'$viewContentLoading'和 '$viewContentLoaded'监听页面渲染状态:渲染开始和渲染结束。(在控制器中添加如下代码实现监听)
// $viewContentLoading- 当视图开始加载,DOM渲染完成以前触发,该事件将在$scope链上广播此事件。 $scope.$watch('$viewContentLoading',function(event, viewConfig){ alert('模板加载完成前'); }); //$viewContentLoaded- 当视图加载完成,DOM渲染完成以后触发,视图所在的$scope发出该事件。 $scope.$watch('$viewContentLoaded',function(event){ alert('模板加载完成后'); });
咱们能够用这些监听事件来判断用户传参,权限断定等等。
咱们了解一下uiRouter路由的工做原理
大体能够理解为:一个 查找匹配 的过程。就是将 hash值 (#xxx)与一系列的 '路由规则' 进行查找匹配,匹配出一个符合条件的规则,而后根据这个规则,进行数据的获取,以及页面的渲染。
咱们分两步学习
咱们经过调用 $stateProvider.state(...) 方法,建立了一个简单路由规则(详情看上文)
当咱们反问http://...index.html#/dash.html的时候,这个路由规则被匹配到,对应的模板会被填到某个 [ui-view] 中。
它作了些什么呢。首先,建立并存储一个state对象,里面包含着该路由规则的全部配置信息。
而后,调用 $urlRouterProvider.when(...) 方法(上文说过ui-router是基于ngRouter),进行路由的 注册 (以前是路由的建立),代码里是这样写的:
$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。
至于说为何说 "跳转到该state,而不是该url"? 其实 ui.router是基于state(状态)的,而不是url,以前就说过,路由存在着明确的 父子关系 ,每个路由能够理解为一个state,当程序匹配到某一个子路由时,咱们就认为这个子路由state被激活,同时,它对应的父路由state也将被激活。咱们还能够手动的激活某一个state,就像上面写的那样, $state.transitionTo(state, ...) ,这样的话,它的父state会被激活(若是尚未激活的话),它的子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();
看懂的朋友就会发现一个问题,每次路由变化(hash变化),因为监听‘$locationChangeSuccess'事件,都要进行rules的遍历 来查找匹配路由,而后跳转到对应的state。咱们之因此要循环遍历rules,是由于要查找匹配到对应的路由(state),而后跳转过去,假若不循环,也是能直接找到对应的state。在用ui.router在建立路由时:会实例化一个对应的state对象,并存储起来(states集合里面)。每个state对象都有一个state.name进行惟一标识(如:'dash')。这时候就体现出 ui-sref指令的大做用了。
<a ui-sref="dash">经过ui-sref跳转到dash.html</a>
当点击这个a标签时,会直接跳转到dash.html,而并不须要循环遍历rules。这个元素折行了一个方法,仍是直接看代码
element.bind("click", function(e) { // .. var transition = $timeout(function() { // 手动跳转到指定的state $state.go(ref.state, params, options); }); });
ui-sref="dash"指令会给对应的dom添加 click事件 ,而后根据dash(state.name),直接跳转到对应的state。
跳转到对应的state以后,ui.router会作一个善后处理,就是改变hash,此时就会触发’$locationChangeSuccess'事件,而后执行回调,可是在回调中能够经过一个判断代码规避循环rules。
代码段:
function update(evt) { var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl; // 手动调用$state.go(...)时,直接return避免下面的循环 if (ignoreUpdate) return true; }
因此咱们在使用中可经过ui-serf来实现路由,达到视图切换,或者在controller中调用 $state.go(....)来实现。
路由介绍的就这么多,可是任然要仔细逐行阅读。也有一部分拓展,项目中没有实际用到,可是也算是给优化作一些思路。与你们共勉。