资讯专栏INFORMATION COLUMN

Angular 2+ 监听路由变化动态设置页面标题

entner / 3166人阅读

摘要:今天我们实现单页面应用中路由变化设置页面标题,来优化用户的用户体验。在中,解决起来要比容易得多,我们可以通过注入一个,在路由变化事件中使用提供的来动态更新页面标题。

现在很多web网站都采用了SPA单页应用,单页面有很多优点:用户体验好、应用响应快、对服务器压力小 等等。同时也有一些缺点:首次加载资源太多,不利于SEO,前进、后退、地址栏需要手动管理。今天我们实现Angular单页面应用中路由变化设置页面标题,来优化用户的用户体验。可以先去掘金看下效果。稀土掘金

在AngularJS(1.x)中动态设置页面标题通常是通过一个全局$rootScope对象来完成的,通过$rootScope对象监听路由变化获取当前路由信息并映射到页面标题。在Angular(v2 +)中,解决起来要比1.x容易得多,我们可以通过注入一个provider,在路由变化事件中使用provider提供的API来动态更新页面标题。

Title Service

在angular中,我们可以通过Title来设置页面标题。我们从platform-browser导入Title, 同时也导入Router

import { Title } from "@angular/platform-browser";
import { Router } from "@angular/router";

导入之后,我们在组件的构造函数中注入他们

@Component({
  selector: "app-root",
  templateUrl: `
    
Hello world!
` }) export class AppComponent { constructor(private router: Router, private titleService: Title) {} }

在使用Title之前,我们先看下Title是如何定义的

export class Title {
  /**
   * Get the title of the current HTML document.
   * @returns {string}
   */
  getTitle(): string { return getDOM().getTitle(); }

  /**
   * Set the title of the current HTML document.
   * @param newTitle
   */
  setTitle(newTitle: string) { getDOM().setTitle(newTitle); }
}

Title类有两个方法,一个用来获取页面标题getTitle, 一个是用来设置页面标题的setTitle

要更新页面标题,我们可以简单的调用setTitle方法:

@Component({...})
export class AppComponent implements OnInit {
  constructor(private router: Router, private titleService: Title) {}
  ngOnInit() {
    this.titleService.setTitle("My awesome app");
  }
}

这样就可以设置我们的页面标题了,但是很不优雅。我们接着往下看。

在AngularJS中,我们可以使用ui-router为每个路由添加一个自定义对象,自定义的对象在路由器的状态链中继承:

// AngularJS 1.x + ui-router
.config(function ($stateProvider) {
  $stateProvider
    .state("about", {
      url: "/about",
      component: "about",
      data: {
        title: "About page"
      }
    });
});

在Angular2+中,我们也可以为每个路由定义一个data对象,然后再在监听路由变化时做一些额外的逻辑处理就可以实现动态设置页面标题。首先,我们定义一个基本的路由:

const routes: Routes = [{
  path: "calendar",
  component: CalendarComponent,
  children: [
    { path: "", redirectTo: "new", pathMatch: "full" },
    { path: "all", component: CalendarListComponent },
    { path: "new", component: CalendarEventComponent },
    { path: ":id", component: CalendarEventComponent }
  ]
}];

在这里定义一个日历应用,他有一个路由/calendar, 还有三个子路由, /all对应日历列表页,new对应新建日历,:id对应日历详情。现在,我们定义一个data对象然后设置一个title属性来作为每个页面的标题。

const routes: Routes = [{
  path: "calendar",
  component: CalendarComponent,
  children: [
    { path: "", redirectTo: "new", pathMatch: "full" },
    { path: "all", component: CalendarListComponent, data: { title: "My Calendar" } },
    { path: "new", component: CalendarEventComponent, data: { title: "New Calendar Entry" } },
    { path: ":id", component: CalendarEventComponent, data: { title: "Calendar Entry" } }
  ]
}];

好了,路由定义完了,现在我们看下如何监听路由变化

Routing events

Angular路由配置非常简单,但是路由通过Observables使用起来也非常强大。
我们可以在根组件中全局监听路由的变化:

ngOnInit() {
  this.router.events
    .subscribe((event) => {
      // example: NavigationStart, RoutesRecognized, NavigationEnd
      console.log(event);
    });
}

我们要做的就是在导航结束时获取到定义的数据然后设置页面标题,可以检查 NavigationStart, RoutesRecognized, NavigationEnd 哪种事件是我们需要的方式,理想情况下NavigationEnd,我们可以这么做:

this.router.events
  .subscribe((event) => {
    if (event instanceof NavigationEnd) { // 当导航成功结束时执行
      console.log("NavigationEnd:", event);
    }
  });

这样我们就可以在导航成功结束时做一些逻辑了,因为Angular路由器是reactive响应式的,所以我们可以使用 RxJS 实现更多的逻辑,我们来导入以下操作符:

import "rxjs/add/operator/filter";
import "rxjs/add/operator/map";
import "rxjs/add/operator/mergeMap";

现在我们已经添加了 filtermapmergeMap 三个操作符,我们可以过滤出导航结束的事件:

this.router.events
  .filter(event => event instanceof NavigationEnd)
  .subscribe((event) => {
    console.log("NavigationEnd:", event);
  });

其次,因为我们已经注入了Router类,我们可以使用 routerState 来获取路由状态树得到最后一个导航成功的路由:

this.router.events
  .filter(event => event instanceof NavigationEnd)
  .map(() => this.router.routerState.root)
  .subscribe((event) => {
    console.log("NavigationEnd:", event);
  });

然而,一个更好的方式就是使用 ActivatedRoute 来代替 routerState.root, 我们可以将其ActivatedRoute注入类中:

import { Router, NavigationEnd, ActivatedRoute } from "@angular/router";

@Component({...})
export class AppComponent implements OnInit {
  constructor(
    private router: Router,
    private activatedRoute: ActivatedRoute,
    private titleService: Title
  ) {}
  ngOnInit() {
    // our code is in here
  }
}

注入之后我们再来优化下:

this.router.events
  .filter(event => event instanceof NavigationEnd)
  .map(() => this.activatedRoute)
  .subscribe((event) => {
    console.log("NavigationEnd:", event);
  });

我们使用 map 转换了我们观察到的内容,返回一个新的对象 this.activatedRoutestream 流中继续执行。 我们使用 filter(过滤出导航成功结束)map(返回我们的路由状态树) 成功地返回我们想要的事件类型 NavigationEnd

接下来是最有意思的部分,我们将创建一个while循环遍历状态树得到最后激活的 route,然后将其作为结果返回到流中:

this.router.events
  .filter(event => event instanceof NavigationEnd)
  .map(() => this.activatedRoute)
  .map(route => {
    while (route.firstChild) route = route.firstChild;
    return route;
  })
  .subscribe((event) => {
    console.log("NavigationEnd:", event);
  });

接下来我们可以通过路由配置的属性来获取相应的页面标题。然后,我们还需要另外两个运算符:

this.router.events
  .filter(event => event instanceof NavigationEnd)
  .map(() => this.activatedRoute)
  .map(route => {
    while (route.firstChild) route = route.firstChild;
    return route;
  })
  .filter(route => route.outlet === "primary")  // 过滤出未命名的outlet,
  .mergeMap(route => route.data)                // 获取路由配置数据
  .subscribe((event) => {
    console.log("NavigationEnd:", event);
  });

现在我们 titleService 只需要实现:

.subscribe((event) => this.titleService.setTitle(event["title"]));

下面看一下最终代码:

import "rxjs/add/operator/filter";
import "rxjs/add/operator/map";
import "rxjs/add/operator/mergeMap";

import { Component, OnInit } from "@angular/core";
import { Router, NavigationEnd, ActivatedRoute } from "@angular/router";
import { Title } from "@angular/platform-browser";

@Component({...})
export class AppComponent implements OnInit {
  constructor(
    private router: Router,
    private activatedRoute: ActivatedRoute,
    private titleService: Title
  ) {}
  ngOnInit() {
    this.router.events
      .filter(event => event instanceof NavigationEnd)
      .map(() => this.activatedRoute)
      .map(route => {
        while (route.firstChild) route = route.firstChild;
        return route;
      })
      .filter(route => route.outlet === "primary")
      .mergeMap(route => route.data)
      .subscribe((event) => this.titleService.setTitle(event["title"]));
  }
}

本文翻译自dynamic-page-titles-angular-2-router-events, 本人水平有限,如果有翻译不好的地方欢迎大家联系我

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/83725.html

相关文章

  • Angular页面动态修改title[兼容微信]

    摘要:文件定义在通过路由定义标题首页首页通过动态调用标题在里面定义通过监听的变化来动态调用标题首页首页文件在里同过头部里的动态调用首页 js文件 定义module var app = angular.module(app, [ngRoute]); 在config通过路由定义标题 app.config([$routeProvider, $locationProvider, function ...

    Cheriselalala 评论0 收藏0
  • AngularJs功能(九)--路由

    摘要:该内的内容会根据路由的变化而变化。配置,用来定义路由规则。由此我们就需要另一个第三方路由模块,叫做,当然它是基于开发的。造成这种现象的最根本原因路由没有明确的父子层级关系。监听路由路由状态发生改变时可以通过监听,通过注入实现状态的管理。 何为路由 路由机制运可以实现多视图的单页Web应用(single page web application,SPA)。 单页应用在使用期间不会重新加载...

    mingde 评论0 收藏0
  • AngularJs

    摘要:当左右服务都被解析并返回时,会以服务为参数去调用组件的构造函数。发送或广播的消息应该限定在最小的作用域。置顶一个通过,发送的消息列表并且窒息的管理以防止命名冲突在需要格式化数据时,将格式 angular 数据双向绑定的框架 提供数据绑定,DOM指令。angular,定义了一套规则,开发中就必须遵守规则,这套规则为项目提供了一套解决方案。 模块,组件,模板,元数据,数据绑定, 指令,服务...

    sf190404 评论0 收藏0
  • Angular 4 简单入门笔记

    摘要:首先,我们需要在入口页面的中配置根路径然后创建一个路由模块路由配置在主模块中导入配置好的路由模块而在页面中需要一个容器去承载上面代码中的定义了用户点击后的路由跳转,定义该路由激活时的样式类。 刚实习的时候用过AngularJS,那时候真的是连原生JavaScript都不会写,依样画葫芦做了几个管理后台。然后突然换项目了,AngularJS就不写了,感觉前前后后接触了一年多的Angula...

    whlong 评论0 收藏0
  • 自己动手实现一个前端路由

    摘要:监听的变动省略其他代码省略其他代码这样,我们就初步实现了一个路由,那么接下来,我们来看看路由怎么实现。 前言 用过现代前端框架的同学,对前端路由一定不陌生, vue, react, angular 都有自己的 router, 那么你对 router 的工作原理了解吗?如果还不了解, 那么请跟我一起来手写一个简单的前端路由, 顺便了解一下. 实现路由的2种方式 hash模式 histo...

    longshengwang 评论0 收藏0

发表评论

0条评论

entner

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<