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 ~