Angular 記錄 - 如何在全局中設計一個路由信息的收集器

引言


在傳統的頁面開發中,咱們經過在瀏覽器中輸入不一樣的頁面路徑,來訪問不一樣的的 html 頁面。但在 SPA(single page application)頁面,咱們的框架經過匹配不一樣的路由地址,來按照一種約定的規則動態將咱們頁面裏的內容替換爲咱們在模板中編寫的內容。css

在業務設計中,咱們可能經過路由進行信息的傳遞、收集。對於一些面向 C 端的產品,可能還須要統計不一樣的路由訪問的次數,對相關信息進行上報、分析等。html

在開發過程當中,咱們則須要對路由的事件進行監聽,對一些關鍵的參數,一般也會採用路由傳遞參數。typescript

所以,不論在哪一個方面,路由在 SPA 項目中的做用都是相當重要的。api

需求分析


在業務開發初期,對於路由的設計會相對簡單。但隨着業務的不斷拓展,極可能會須要對動態路由導航的支持。例如對於下面這種路由導航:瀏覽器

該組件,支持對數據的實時展現,也支持動態時間段的篩選。跟過路由,即可以大概獲取到關鍵性的一些參數。服務器

咱們來查看他的路由結構:app

import { Routes } from '@angular/router';
...
export const routing: Routes = [
    {
        path: '',
        component: MainPageComponent,
        children: [
            {
                path: UrlPath.REAL_TIME,
                children: [
                    {
                        path: '',
                        pathMatch: 'full',
                        redirectTo: '/' + UrlPath.MAIN
                    },
                    {
                        path: ':' + UrlPathId.APPLICATION,
                        resolve: {
                            serverTime: ServerTimeResolverService
                        },
                        data: {
                            showRealTimeButton: true,
                            enableRealTimeMode: true
                        },
                        component: MainContentsContainerComponent
                    }
                ]
            },
            {
                path: ':' + UrlPathId.APPLICATION,
                children: [
                    {
                        path: '',
                        pathMatch: 'full',
                        data: {
                            path: UrlPath.MAIN
                        },
                        component: UrlRedirectorComponent
                    },
                    {
                        path: ':' + UrlPathId.PERIOD,
                        children: [
                            {
                                path: '',
                                pathMatch: 'full',
                                data: {
                                    path: UrlPath.MAIN
                                },
                                component: UrlRedirectorComponent
                            },
                            {
                                path: ':' + UrlPathId.END_TIME,
                                data: {
                                    showRealTimeButton: true,
                                    enableRealTimeMode: false
                                },
                                component: MainContentsContainerComponent
                            }
                        ]
                    }
                ]
            },
            {
                path: '',
                pathMatch: 'full',
                component: EmptyContentsComponent,
            }
        ]
    },
    {
        path: '**',
        component: MainPageComponent
    },
];

複製代碼

對於這種參數不固定的動態路由,若是此時咱們須要針對路由信息進行上報、根據路由參數動態匹配組件,並進行路由參數的提取,咱們有如下兩種處理方案:框架

  • 在每一個須要的地方去注入路由實例,獲取對應的信息。在不一樣的頁面初始化的時候,對頁面的信息進行上報。ui

  • 在全局註冊一個服務(service), 用於收集路由信息, 根據當前信息去匹配出一些必要的參數。並在 service 中統一管理對路由事件的上報。this

哪種方案更好,這裏就不討論了。

設計路由收集器


根據需求,在全局位置上,咱們會有一個路由收集器,負責收集頁面中的參數。 咱們經過 ActivatedRoute 來獲取當前組件相關的路由信息, 並實現一個方法將路由中的信息提取出來,設置在 service 中。

ActivatedRoute 可用於遍歷路由器的狀態
經常使用的屬性有:

     snapshot, 用於獲取當前路由樹的快照信息。
     params, 返回當前路由範圍內矩陣參數的observable。
     queryParams, 返回當前路由範圍內查詢參數的observable。
     firstChild, 用於獲取當前路由的第一個子路由。
     children, 用於獲取當前路由的全部子路由。

關於 ActivatedRoute 更多使用,能夠參考官方文檔

編寫 route-collector.interface.ts 聲明文件:

// route-collector.interface.ts
import { ParamMap, ActivatedRoute } from '@angular/router';

export interface RouteCollectorInterface {

    collectUrlInfo(activatedRoute: ActivatedRoute): void;

    setData(dataMap: Map<string, string>, routeData: ParamMap): void;
}
複製代碼

實現路由搜索服務


根據設計好的路由收集的接口,咱們須要去編寫具體的業務邏輯。在 app.module.ts 中,注入一個 service 服務,並實現 RouteCollectorInterface 接口:

// route-collect.service.ts
import { Injectable } from '@angular/core';
import { ParamMap, ActivatedRoute } from '@angular/router';

@Injectable()
export class RouteCollectorService implements RouteInfoCollectorService {
    constructor( private routeManageService: RouteManagerService, private analyticsService: AnalyticsService, ) {}

    collectUrlInfo(activatedRoute: ActivatedRoute): void {
        console.log('activatedRoute ==>', activatedRoute);
    }

    private setData(dataMap: Map<string, string>, routeData: ParamMap): void {
        routeData.keys.forEach((key: string) => {
            dataMap.set(key, routeData.get(key));
        });
    }
}
複製代碼

在 app.component.ts 中,咱們在頁面初始化, 去監聽路由的變化事件:

// app.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';
import { RouteCollectorService } from './service';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
    constructor( private routeCollectorService: RouteCollectorService, private router: Router, private activatedRoute: ActivatedRoute, ) {}

    ngOnInit() {
        this.listenToRouteChange();
    }

    private listenToRouteChange(): void {
        this.router.events.pipe(
            filter(event => event instanceof NavigationEnd)
        ).subscribe(() => {
            this.routeCollectorService.collectUrlInfo(this.activatedRoute);
        });
    }
}
複製代碼

在根組件中,咱們監聽 NavigationEnd 事件,將此時的路由信息 activatedRoute 傳遞給 collectUrlInfo 方法。運行項目,打開控制檯,咱們能夠看到輸出信息:

查看當前路由快照信息:

能夠看到,ActivatedRoute 在快照中保留了當前組件路由的組件樹

遍歷路由樹,取出所要的數據:

// route-collect.service.ts
import { Injectable } from '@angular/core';
import { ParamMap, ActivatedRoute } from '@angular/router';

@Injectable()
export class RouteCollectorService implements RouteInfoCollectorService {
    constructor( private routeManageService: RouteManagerService, private analyticsService: AnalyticsService, ) {}

    collectUrlInfo(activatedRoute: ActivatedRoute): void {
        // 定義 路由參數 Map, 及路由查詢參數 Map
        const pathParamMap = new Map<string, string>();
        const queryParamMap = new Map<string, string>();
        let routeChild = activatedRoute.snapshot.firstChild;
        while (routeChild) {
            // 設置路由參數
            this.setParamsMap(pathParamMap, routeChild.paramMap);
            // 設置路由查詢參數
            this.setParamsMap(queryParamMap, routeChild.queryParamMap);
            routeChild = routeChild.firstChild;
        }
        console.dir('pathParamMap', pathParamMap);
        console.dir('queryParamMap', queryParamMap);
    }
    
    // 用於提取路由參數及路由查詢參數 Map 中的信息
    private setParamsMap(dataMap: Map<string, string>, routeData: ParamMap): void {
        routeData.keys.forEach((key: string) => {
            dataMap.set(key, routeData.get(key));
        });
    }
}
複製代碼

經過 while 循環,遍歷全部的子路由,取出對應的 params 信息並收集到 Map 中,打印結果能夠看到:

當前路由信息,已經成功被實時收集了。對於項目中咱們本身設置的 路由配置參數的獲取,咱們還須要作進一步處理:

// route-collect.service.ts
...
    collectUrlInfo(activatedRoute: ActivatedRoute): void {
        // 定義 路由參數 Map, 及路由查詢參數 Map
        const pathParamMap = new Map<string, string>();
        const queryParamMap = new Map<string, string>();
        let routeChild = activatedRoute.snapshot.firstChild;
        let configData = {};
        while (routeChild) {
            // 設置路由參數
            this.setParamsMap(pathParamMap, routeChild.paramMap);
            // 設置路由查詢參數
            this.setParamsMap(queryParamMap, routeChild.queryParamMap);
            // 設置路由配置信息
            configData = { ...configData, ...routeChild.data };
            routeChild = routeChild.firstChild;
        }
        console.dir('configData', configData);
    }
...
複製代碼

打印配置信息,能夠看到:

能夠看到,針對當前路由配置的 data 和狀態參數都被獲取過來了。

路由信息使用建議


完成了對路由參數、路由查詢參數、路由配置信息的動態獲取。咱們只須要在咱們須要的地方保存咱們的路由信息,在一個合適的時機去發起 track 動做,來向服務器彙報咱們的路由信息了。

關於路由信息儲存,這裏推薦兩種保存信息的方案:

  • 使用 service 服務中保存路由信息,在須要的地方注入對應的 service 實例便可。
  • 將數據保存在 @ngrx/store 狀態中。

對於方案一,咱們能夠在對應的 service 中隨意操做咱們收集到信息,這種方式在全局可用,使用的時候只須要注入對應實例便可,拓展性較強,缺點是咱們須要額外定義 Observable 流來廣播咱們的路由信息,須要多寫代碼去和組件中其餘的 Observable 組合來使用。

對於方案二,咱們在 @ngrx/store 中保存咱們路由信息的狀態,經過定義不一樣的 Action 來操做咱們的路由信息便可。這種方式在配合 @ngrx/effect 反作用的時候,在 組件 和 service 中獲取該信息都變的更加方便,而且 Ngrx 會幫咱們把這種獲取轉換爲 Observable 流方便咱們在組件中結合其餘 Observable 使用,缺點是須要額外引入 @ngrx 第三方業務體系。

兩種方案各有利弊,在使用中還須要結合具體的業務場景來作。

總結


在項目中,咱們常常配置各類路由來渲染咱們的組件並進行參數傳遞。對於這種業務,應考慮在一個特定的地方去處理路由信息,以某種機制來統一分發路由狀態,將業務中所須要的全部信息統一收集,統一分發,統一上報。

這樣能夠在必定程度上減輕組件內與路由相關的業務。

感謝您的閱讀~

相關文章
相關標籤/搜索