The introduction


In traditional page development, we access different HTML pages by entering different page paths into the browser. But in SPA (Single Page Application) pages, our framework dynamically replaces the content in our page with the content we wrote in the template by matching different routing addresses according to a convention.

In business design, we may transmit and collect information through routing. For c-terminal products, you may need to collect statistics on the number of access routes and report and analyze related information.

In the development process, we need to monitor the routing events, for some key parameters, usually also use routing parameters.

Therefore, the role of routing in SPA projects is crucial in every respect.

Demand analysis


At the beginning of service development, routing design is relatively simple. However, as the business continues to expand, it is likely to need the support of dynamic routing navigation. For example, for the following route navigation:

This component supports real-time display of data and filtering of dynamic time periods. The key parameters can be roughly obtained by following the routing.

Let’s look at its routing structure:

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
    },
];

Copy the code

For such dynamic routes with unfixed parameters, if we need to report routing information, dynamically match components according to routing parameters, and extract routing parameters, we have the following two processing schemes:

  • Inject route instances wherever needed to get the corresponding information. During the initialization of different pages, the page information is reported.

  • Register a service globally to collect routing information and match necessary parameters based on the current information. In service, route events are reported in a unified manner.

Which scheme is better is not discussed here.

Designing a Routing collector


As required, at the global location, we will have a routing collector that collects the parameters in the page. We used the ActivatedRoute to obtain the route information of the current component and implemented a method to extract the route information and set it in the Service.

ActivatedRoute can be used to traverse router status. The following attributes are commonly used: snapshot: Used to obtain snapshot information about the current routing tree. Params, an Observable that returns matrix parameters in the current route scope. QueryParams, returns the Observable of the query parameters in the current route scope. FirstChild to get the firstChild of the current route. Children, used to get all the children of the current route. For more information about ActivatedRoute, please refer to the official documentation

Declare 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;
}
Copy the code

Implement the route search service


According to the interface designed for route collection, we need to write the specific business logic. In app.module.ts, inject a service and implement the RouteCollectorInterface:

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

@Injectable(a)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)); }); }}Copy the code

In app.component.ts, we initialize the page to listen for route changes:

// 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((a)= > {
            this.routeCollectorService.collectUrlInfo(this.activatedRoute); }); }}Copy the code

In the root component, we listen for the NavigationEnd event and pass the route information activatedRoute to the collectUrlInfo method. Run the project, open the console, and we can see the output:

View the current route snapshot information:

As you can see, the ActivatedRoute preserves the component tree of the current component route in the snapshot.

Traverse the routing tree and fetch the desired data:

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

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

    collectUrlInfo(activatedRoute: ActivatedRoute): void {
        // Define route parameter Map and route query parameter Map
        const pathParamMap = new Map<string.string> ();const queryParamMap = new Map<string.string> ();let routeChild = activatedRoute.snapshot.firstChild;
        while (routeChild) {
            // Set route parameters
            this.setParamsMap(pathParamMap, routeChild.paramMap);
            // Set route query parameters
            this.setParamsMap(queryParamMap, routeChild.queryParamMap);
            routeChild = routeChild.firstChild;
        }
        console.dir('pathParamMap', pathParamMap);
        console.dir('queryParamMap', queryParamMap);
    }
    
    // Used to extract route parameters and route query parameter Map
    private setParamsMap(dataMap: Map<string.string>, routeData: ParamMap): void {
        routeData.keys.forEach((key: string) = >{ dataMap.set(key, routeData.get(key)); }); }}Copy the code

Through the while loop, all subroutes are iterated, corresponding params information is extracted and collected into Map, and the printed result can be seen as follows:

Route configuration parameters

// route-collect.service.ts. collectUrlInfo(activatedRoute: ActivatedRoute):void {
        // Define route parameter Map and route query parameter Map
        const pathParamMap = new Map<string.string> ();const queryParamMap = new Map<string.string> ();let routeChild = activatedRoute.snapshot.firstChild;
        let configData = {};
        while (routeChild) {
            // Set route parameters
            this.setParamsMap(pathParamMap, routeChild.paramMap);
            // Set route query parameters
            this.setParamsMap(queryParamMap, routeChild.queryParamMap);
            // Set the route configurationconfigData = { ... configData, ... routeChild.data }; routeChild = routeChild.firstChild; }console.dir('configData', configData); }...Copy the code

Print the configuration information and you can see:

As you can see, the data and status parameters configured for the current route are fetched.

Suggestions for using routing information


Dynamic route parameters, route query parameters, and route configuration information are obtained. We just need to save our routing information in the place we need, and initiate track action at an appropriate time to report our routing information to the server.

Two schemes are recommended for saving routing information:

  • Use service to save routing information and inject service instances where required.
  • Save the data in the @ngrx/ Store state.

For scheme 1, we can operate freely in the corresponding service to collect the information. This method is globally available and only needs to inject the corresponding instance when using it, which is highly extensible. The disadvantage is that we need to define an Extra Observable stream to broadcast our routing information. You need to write more code to combine it with other Observables in the component.

For scheme 2, we store the state of our routing information in @ngrx/store and define different actions to manipulate our routing information. This way, when combined with the @Ngrx /effect side effects, it becomes more convenient to get this information from components and services. Moreover, NGRX helps us convert this information into an Observable flow, which is convenient for us to use with other Observables in components. The disadvantage is the additional introduction of @Ngrx third party business system.

The two solutions have their own advantages and disadvantages, and need to be combined with specific business scenarios.

conclusion


In projects, we often configure various routes to render our components and pass parameters. To process routing information in a specific place, use a mechanism to distribute routing status and collect, distribute, and report all information required by services in a unified manner.

This can reduce routing-related services within components to some extent.

Thank you for reading ~