Angular 2+ 監聽路由變化動態設置頁面標題

如今不少web網站都採用了SPA單頁應用,單頁面有不少優勢:用戶體驗好、應用響應快、對服務器壓力小 等等。同時也有一些缺點:首次加載資源太多,不利於SEO,前進、後退、地址欄須要手動管理。今天咱們實現Angular單頁面應用中路由變化設置頁面標題,來優化用戶的用戶體驗。能夠先去掘金看下效果。稀土掘金html

在AngularJS(1.x)中動態設置頁面標題一般是經過一個全局$rootScope對象來完成的,經過$rootScope對象監聽路由變化獲取當前路由信息並映射到頁面標題。在Angular(v2 +)中,解決起來要比1.x容易得多,咱們能夠經過注入一個provider,在路由變化事件中使用provider提供的API來動態更新頁面標題。react

Title Service

在angular中,咱們能夠經過Title來設置頁面標題。咱們從platform-browser導入Title, 同時也導入Routergit

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

導入以後,咱們在組件的構造函數中注入他們github

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

在使用Title以前,咱們先看下Title是如何定義的web

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, 一個是用來設置頁面標題的setTitleapi

要更新頁面標題,咱們能夠簡單的調用setTitle方法:服務器

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

這樣就能夠設置咱們的頁面標題了,可是很不優雅。咱們接着往下看。app

在AngularJS中,咱們可使用ui-router爲每一個路由添加一個自定義對象,自定義的對象在路由器的狀態鏈中繼承:ide

// 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,<router-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, 本人水平有限,若是有翻譯很差的地方歡迎你們聯繫我

相關文章
相關標籤/搜索