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 第三方业务体系。

两种方案各有利弊,在使用中还须要结合具体的业务场景来作。

总结


在项目中,咱们常常配置各类路由来渲染咱们的组件并进行参数传递。对于这种业务,应考虑在一个特定的地方去处理路由信息,以某种机制来统一分发路由状态,将业务中所须要的全部信息统一收集,统一分发,统一上报。

这样能够在必定程度上减轻组件内与路由相关的业务。

感谢您的阅读~

相关文章
相关标签/搜索