Angular学习笔记(7)—多重视图和路由

  能够从页面的一个视图跳转到另外一个视图,对单页面应用来讲是至关重要的。
  除了用ng-include指令在视图中引用多个模板外,更好的做法是将视图分解成布局和模板视图,并且根据用户当前访问的URL来展示对应的视图。
  我们会将这些模板分解到视图中,并在布局模板内进行组装。AngularJS允许我们在$route服务的提供者$routeProvider中通过声明路由来实现这个功能。

安装

  从1.2版本开始,AngularJS将ngRoutes从核心代码中剥离出来成为独立的模块。我们需要安装并引用它,才能够在AngularJS应用中正常地使用路由功能。
  可以从http://code.angularjs.org 下载它,然后保存到一个可以在HTML页面中进行引用的位置。也可以用Bower来安装,这样会将它存放到Bower的目录中。

$bower instal l--save angular-route

  在HTML中,需要在AngularJS之后引用angular-route

<script src="js/vendor/angular.js"></script>
<script src="js/vendor/angular-route.js"></script>

  最后,要把ngRoute模块在我们的应用中当作依赖加载进来:

angular.module('myApp', ['ngRoute']);

布局模版

  要创建一个布局模板,需要修改HTML以告诉AngularJS把模板渲染到何处。通过将ng-view指令和路由组合到一起,我们可以精确地指定当前路由所对应的模板在DOM中的渲染位置。
例如:

<header>
    <h1>Header</h1>
</header>
<div class="content">
    <div ng-view></div>
</div>
<footer>
    <h5>Footer</h5>
</footer>

这个例子中,我们将所有需要渲染的内容都放到了<div class="content">中,而<header><footer>中的内容在路由改变时不会有任何变化。
ng-view是由ngRoute模块提供的一个特殊指令,它的独特作用是在HTML中给$route对应的视图内容占位。
它会创建自己的作用域并将模板嵌套在内部。
ng-view是一个优先级为1000的终极指令。AngularJS不会运行同一个元素上的低优先级指令(例如<div ng-view></div>元素上其他指令都是没有意义的)。
ngView指令遵循以下规则。

  • 每次触发$routeChangeSuccess事件,视图都会更新。
  • 如果某个模板同当前的路由相关联:
  • 创建一个新的作用域;
  • 移除上一个视图,同时上一个作用域也会被清除;
  • 将新的作用域同当前模板关联在一起;
  • 如果路由中有相关的定义,那么就把对应的控制器同当前作用域关联起来;
  • 触发$viewContentLoaded事件;
  • 如果提供了onload属性,调用该属性所指定的函数。

路由

  我们可以使用AngularJS提供的whenotherwise两个方法来定义应用的路由。用config函数在特定的模块或应用中定义路由。

angular.module('myApp', []).config(['$routeProvider', function($routeProvider) {
    // 在这里定义路由
}]);

  现在,我们可以用when方法来添加一个特定的路由。这个方法可以接受两个参数。
下面的例子展示了如何创建一个独立的路由:

angular.module('myApp', []).config(['$routeProvider', function($routeProvider) {
    $routeProvider.when('/', {
        templateUrl: 'views/home.html',
        controller: 'HomeController'
    });
}]);

  第一个参数是路由路径,这个路径会与$location.path进行匹配,$location.path也就是
当前URL的路径。如果路径后面还有其他内容,或使用了双斜线也可以正常匹配。我们可以在URL中存储参数,参数需要以冒号开头(例如:name),用$routeParams可以读取这些参数。
第二个参数是配置对象,决定了当第一个参数中的路由能够匹配时具体做些什么。配置对象中可以进行设置的属性包括controllertemplatetemplateURLresolveredirectToreloadOnSearch

angular.module('myApp', []).
    config(['$routeProvider', function($routeProvider) {
        $routeProvider
          .when('/', {
            templateUrl: 'views/home.html',
            controller: 'HomeController'
          })
          .when('/login', {
            templateUrl: 'views/login.html',
            controller: 'LoginController'
          })
          .when('/dashboard', {
            templateUrl: 'views/dashboard.html',
            controller: 'DashboardController',
            resolve: {
                user: function(SessionService) {
                    return SessionService.getCurrentUser();
                }
            }
          })
          .otherwise({
            redirectTo: '/'
          });
    }]);
1.controller
controller: 'MyController'
// 或者
controller: function($scope) {}

  如果配置对象中设置了controller属性,那么这个指定的控制器会与路由所创建的新作用域关联在一起。如果参数值是字符型,会在模块中所有注册过的控制器中查找对应的内容,然后与路由关联在一起。如果参数值是函数型,这个函数会作为模板中DOM元素的控制器并与模板进行关联。

2.template
template: '<div><h2>Route</h2></div>'

  AngularJS会将配置对象中的HTML模板渲染到对应的具有ng-view指令的DOM元素中。

3.templateUrl
templateUrl: 'views/template_name.html'

  应用会根据templateUrl属性所指定的路径通过XHR读取视图(或者从$templateCache中读取)。如果能够找到并读取这个模板,AngularJS会将模板的内容渲染到具有ng-view指令的DOM元素中。

4.resolve
resolve: {
    'data': ['$http', function($http) {
        return $http.get('/api').then(
            function success(resp) { return response.data; },
            function error(reason) { return false; }
        );
    }];
}

  如果设置了resolve属性,AngularJS会将列表中的元素都注入到控制器中。如果这些依赖是promise对象,它们在控制器加载以及$routeChangeSuccess被触发之前,会被resolve并设置成一个值。
列表对象可以是:

  • 键,键值是会被注入到控制器中的依赖的名字;
  • 工厂,即可以是一个服务的名字,也可以是一个返回值,它是会被注入到控制器中的函数或可以被resolvepromise对象。

  在上面的例子中,resolve会发送一个$http请求,并将data的值替换为返回结果的值。列表中的键data`会被注入到控制器中,所以在控制器中可以使用它。

5.redirectTo
redirectTo: '/home'
// 或者
redirectTo: function(route,path,search)

  如果redirectTo属性的值是一个字符串,那么路径会被替换成这个值,并根据这个目标路径触发路由变化。
  如果redirectTo属性的值是一个函数,那么路径会被替换成函数的返回值,并根据这个目标路径触发路由变化。
  如果redirectTo属性的值是一个函数,AngularJS会在调用它时传入下面三个参数中:
  (1) 从当前路径中提取出的路由参数;
  (2) 当前路径;
  (3) 当前URL中的查询串。

6.reloadOnSearch

  如果reloadOnSearch选项被设置为true(默认),当$location.search()发生变化时会重新加载路由。如果设置为false,那么当URL中的查询串部分发生变化时就不会重新加载路由。
  下面的例子中设置了两个路由:一个首页路由和一个收件箱路由,同时首页路由被设置成默认路由。

angular.module('MyApp', []).config(['$routeProvider', function($routeProvider) {
    $routeProvider.when('/', {
        controller: 'HomeController',
        templateUrl: 'views/home.html'
    })
    .when('/inbox/:name', {
        controller: 'InboxController',
        templateUrl: 'views/inbox.html'
    })
    .otherwise({redirectTo: '/'});
}]);

  otherwise方法会在没有任何路由匹配时被调用,我们用它设置了一个默认跳转到'/'路径的路由。
  当浏览器加载AngularJS应用时,会将URL设置成默认路由所指向的路径。除非我们在浏览器中加载不同的URL,否则默认会使用'/'路由。

$routeParams

  如果我们在路由参数的前面加上:,AngularJS就会把它解析出来并传递给$routeParams
例如,如果我们设置下面这样的路由:

$routeProvider
  .when('/inbox/:name', {
      controller: 'InboxController',
      templateUrl: 'views/inbox.html'
  });

  AngularJS会在$routeParams中添加一个名为name的键,它的值会被设置为加载进来的URL中的值。
  如果浏览器加载/inbox/all这个URL,那么$routeParams对象看起来会是这样:{ name: 'all' }。需要注意,如果想要在控制器中访问这些变量,需要把$routeParams注入进控制器:

app.controller('InboxController', function($scope,$routeParams) {
     // 在这里访问$routeParams
});

$location 服务

  AngularJS提供了一个服务用以解析地址栏中的URL,并让你可以访问应用当前路径所对应的路由。它同样提供了修改路径和处理各种形式导航的能力。
  $location服务对JS中的window.location对象的API进行了更优雅地封装,并且和AngularJS集成在一起。
  当应用需要在内部进行跳转时是使用$location服务的最佳场景,比如当用户注册后、修改或者登录后进行的跳转。
  $location服务没有刷新整个页面的能力。如果需要刷新整个页面,需要使用$window.location对象(window.location的一个接口)。

1.path()

path()用来获取页面当前的路径:

$location.path(); // 返回当前路径

修改当前路径并跳转到应用中的另一个URL:

$location.path('/'); // 把路径修改为'/'路由

path()方法直接和HTML5的历史API进行交互,所以用户可通过点击后退按钮退回到上一个页面。

2. replace()

如果你希望跳转后用户不能点击后退按钮(对于登录之后的跳转这种发生在某个跳转之后的再次跳转很有用),AngularJS提供了replace()方法来实现这个功能:

$location.path('/home');
$location.replace();
// 或者
$location.path('/home').replace();
3. absUrl()

absUrl()方法用来获取编码后的完整URL:

$location.absUrl()
4. hash()

hash()方法用来获取URL中的hash片段:

$location.hash(); // 返回当前的hash片段
5. host()

host()方法用来获取URL中的主机:

$location.host();// 当前URL的主机
6. port()

port()方法用来获取URL中的端口号:

$location.port();// 当前URL的端口
7. protocol()

protocol()方法用来获取URL中的协议:

$location.protocol();// 当前URL的协议
8. search()

search()方法用来获取URL中的查询串:

$location.search();

我们可以向这个方法中传入新的查询参数,来修改URL中的查询串部分:

// 用对象设置查询
$location.search({name: 'Ari', username: 'auser'});
// 用字符串设置查询
$location.search('name=Ari&username=auser');

search方法可以接受两个参数。

  • search(可选,字符串或对象)
    这个参数代表新的查询参数。hash对象的值可以是数组。
  • paramValue(可选,字符串)
    如果search参数的类型是字符串,那么paramValue会做为该参数的值覆盖URL当中的对应值。如果paramValue的值是null,对应的参数会被移除掉。
9. url()

url()方法用来获取当前页面的URL:

$location.url(); // 该URL的字符串

如果调用url()方法时传了参数,会设置并修改当前的URL,这会同时修改URL中的路径、查询串和hash,并返回$location。

// 设置新的URL
$location.url('/home?name=Ari#hashthing');

url()方法可以接受两个参数:

  • url(可选,字符串)
    新的URL的基础的前缀。
  • replace(可选,字符串)
    想要修改成的路径。

路由模式

  不同的路由模式在浏览器的地址栏中会以不同的URL格式呈现。$location服务默认会使用标签模式来进行路由。
  路由模式决定你的站点的URL长成什么样子。

标签模式

  标签(hashbang)是AngularJS用来同你的应用内部进行链接的技巧。标签模式是HTML5模式的降级方案,URL路径会以#符号开头。标签模式不需要重写<a href=""></a>标签,也不需要任何服务器端的支持。如果没有进行额外的指定,AngularJS将默认使用标签模式。
使用标签模式的URL看起来是这样的:

http://yoursite.com/#!/inbox/all

  如果要显式指定配置并使用标签模式,需要在应用模块的config函数中进行配置:

angular.module('myApp', ['ngRoute'])
.config(['$locationProvider', function($locationProvider) {
$locationProvider.html5Mode(false);
}]);

我们还可以配置hashPrefix,也就是标签模式下标签默认的前缀!符号。这个前缀也是AngularJS在比较老的浏览器中降级机制的一部分。这个符号是可以配置的:

angular.module('myApp', ['ngRoute'])
  .config(['$locationProvider', function($locationProvider) {
      $locationProvider.html5Mode(false);
      $locationProvider.hashPrefix('!');
  }]);

HTML5 模式

  AngularJS支持的另外一种路由模式是html5模式。在这个模式中,URL看起来和普通的URL
一样。例如,同样的路由在HTML5模式中看起来是这样的:http://yoursite.com/inbox/all
  在AngularJS内部,$location服务通过HTML5历史API让应用能够使用普通的URL路径来路由。当浏览器不支持HTML5历史API时,$location服务会自动使用标签模式的URL作为替代方案。
  $location服务还有一个有趣的功能,当一个支持HTML5历史API的现代浏览器加载了一个带标签的URL时,它会为用户重写这个URL。
  在HTML5模式中,AngularJS会负责重写<a href=""></a>中的链接。也就是说AngularJS会
根据浏览器的能力在编译时决定是否要重写href=""中的链接。
  例如<a href="/person/42?all=true">Person</a>这个标签,在老式浏览器中会被重写成标签模式的URL:/index.html#!/person/42?all=true。但在现代浏览器中会URL会保持本来的样子。
  后端服务器也需要支持URL重写,服务器需要确保所有请求都返回index.html,以支持HTML5模式。这样才能确保由AngularJS应用来处理路由。
  当在HTML5模式的AngularJS中写链接时,永远都不要使用相对路径。如果你的应用是在根路径中加载的,这不会有什么问题,但如果是在其他路径中,AngularJS应用就无法正确处理路由了。
另一个选择是在HTML文档的HEAD中用<base>标签来指定应用的基础URL:

<base href="/base/url" />

路由事件

  $route服务在路由过程中的每个阶段都会触发不同的事件,可以为这些不同的路由事件设置监听器并做出响应。这个功能对于控制不同的路由事件,以及探测用户的登录和授权状态等场景是非常有用的。
  我们需要给路由设置事件监听器,用$rootScope来监听这些事件。

1. $routeChangeStart

  AngularJS在路由变化之前会广播$routeChangeStart事件。在这一步中,路由服务会开始加载路由变化所需要的所有依赖,并且模板和resolve键中的promise也会被resolve

angular.module('myApp', [])
  .run(['$rootScope', '$location', function($rootScope, $location) {
      $rootScope.$on('$routeChangeStart', function(evt, next, current) {
      });
  }]);

$routeChangeStart事件带有两个参数:

  • 将要导航到的下一个URL;
  • 路由变化前的URL。
2. $routeChangeSuccess

  AngularJS会在路由的依赖被加载后广播$routeChangeSuccess事件。

angular.module('myApp', [])
  .run(['$rootScope', '$location', function($rootScope, $location) {
      $rootScope.$on('$routeChangeSuccess', function(evt, next, previous) {
      });
  }]);

$routeChangeStart事件带有三个参数:

  • 原始的AngularJS evt对象;
  • 用户当前所处的路由;
  • 上一个路由(如果当前是第一个路由,则为undefined)。
3. $routeChangeError

  AngularJS会在任何一个promise被拒绝或者失败时广播$routeChangeError事件。

angular.module('myApp', [])
  .run(function($rootScope, $location) {
      $rootScope.$on('$routeChangeError', function(current, previous, rejection) {
      });
  });

$routeChangeError事件有三个参数:

  • 当前路由的信息;
  • 上一个路由的信息;
  • 被拒绝的promise的错误信息。
4. $routeUpdate

  AngularJS在reloadOnSearch属性被设置为false的情况下,重新使用某个控制器的实例时,会广播$routeUpdate事件。

关于搜索引擎索引

  Web爬虫对于JS的胖客户端应用无能为力。为了在应用的运行过程中给爬虫提供支持,我们需要在头部添加meta标签。这个元标记会让爬虫请求一个带有空的转义片段参数的链接,服务器根据请求返回对应的HTML代码片段。

<meta name="fragment" content="!"/>

更多关于路由的内容

页面重新加载

  $location服务不会重新加载整个页面,它只会单纯地改变URL。如果我们想重新加载整个页面,需要用$window服务来设置地址。

$window.location.href = "/reload/page";

异步的地址变化

  如果我们想要在作用域的生命周期外使用$location服务,必须用$apply函数将变化抛到应用外部。因为$location服务是基于$digest来驱动浏览器的地址变化,以使路由事件正常工作的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容