Overview
How to implement the routing guard interface to meet specific functional requirements and implement lazy loading of feature modules
Corresponding official document address:
- Routing and Navigation
Angular-practice/SRC /router-combat
Contents
- Angular from pit to pit – Getting Started with Angular usage
- Angular From Pothole to Pothole – Component eating Guide
- Angular From pothole to Pothole – Form controls overview
- Angular From Pit to Pit – HTTP request Overview
- Angular uses the entry point north for routing from pit to pit
- Angular goes from pit to pit – Route guard watches
Knowledge Graph
Step by Step
Basis of preparation
Repeat from the previous note to set up an Angualr project with routing configuration
Create four components that correspond to three pages that are actually used and one 404 page that is configured as a wildcard route
Ng g component crisis-list ng g component hero-list ng g component hero-detail404Page ng g Component page-not-foundCopy the code
Complete the definition of project routes in the app-routing.module.ts file, which includes redirection of routes, wildcard routes, and the use of parameter passing through dynamic routes
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
// Import components
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { HeroListComponent } from './hero-list/hero-list.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
const routes: Routes = [
{
path: 'crisis-center',
component: CrisisListComponent,
},
{
path: 'heroes',
component: HeroListComponent,
},
{
path: 'hero/:id',
component: HeroDetailComponent,
},
{
path: ' ',
redirectTo: '/heroes',
pathMatch: 'full',
},
{
path: '* *',
component: PageNotFoundComponent,
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule { }
Copy the code
Then, in the root component, add a router-outlet tag to declare the route to the outlet rendered on the page
<h1>Angular Router</h1>
<nav>
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a> <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
</nav>
<router-outlet></router-outlet>
Copy the code
Routing guard
In Angular, route guards solve the following problems
- Verify the permission of the user to access the page (are you logged in? Do roles that have logged in have access rights?
- Get some necessary data before jumping to the component
- When you leave the page, prompt the user whether to save the uncommitted changes
The Angular routing module provides the following interfaces to help solve this problem
- CanActivate: used to handle the system jumping to a route address (check whether access is available)
- CanActivateChild: functions the same as CanActivate, but for child routes
- CanDeactivate: used to handle departures from the current route (check if uncommitted information exists)
- CanLoad: whether to allow lazy loading of a module
After adding the route guard, we can control the route through the value returned by the route guard
- True: Navigation will continue
- False: The navigation breaks and the user stays on the current page or goes to the specified page
- UrlTree: Cancel the current navigation and navigate to the UrlTree returned by the routing guard (a new routing information)
CanActivate: indicates authentication authorization
Before implementing router guard, you can use the Angular CLI to generate an interface implementation class for router guard. On the command line, generate an authorized guard class in the app/auth path. The CLI prompts you to select the inherited router guard interface, which is CanActivate
ng g guard auth/auth
Copy the code
In the route guard class AuthGuard, we simulate the authentication of whether to allow access to a route address. Check whether you have logged in. If you have logged in, check whether the current user has the access permission of the current routing address
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
/** * ctor * @param router */
constructor(private router: Router) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
// Check whether there is token information
let token = localStorage.getItem('auth-token') | |' ';
if (token === ' ') {
this.router.navigate(['/login']);
return false;
}
// Determine whether the current connection can be accessed
let url: string = state.url;
if (token === 'admin' && url === '/crisis-center') {
return true;
}
this.router.navigate(['/login']);
return false; }}Copy the code
We can then introduce the AuthGuard class in the app-routing.module.ts file to configure the routing guard for the routes that need to be protected
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
// Import components
import { CrisisListComponent } from './crisis-list/crisis-list.component';
// Import the route guard
import { AuthGuard } from './auth/auth.guard';
const routes: Routes = [
{
path: 'crisis-center',
component: CrisisListComponent,
canActivate: [AuthGuard], // Add a canActivate guard for the current route}];@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule { }
Copy the code
CanActivateChild: authentication and authorization for subroutes
Similar to inheriting the CanActivate interface for route guarding, authentication and authorization for subroutes can be implemented by inheriting the CanActivateChild interface. Because the authorization logic is very similar, we extend the Function of AuthGuard through multiple inheritance. In this way, the route guard for both routes and subroutes can be achieved
Modify the implementation of the original canActivate method and modify the authentication logic to allow the user to access the crisis-center page if the token information contains admin. In the canActivateChild method for authentication and authorization of subroutes, Check whether the token information is admin-master to complete access authentication for child routes
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router, CanActivateChild } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate, CanActivateChild {
/** * ctor * @param router */
constructor(private router: Router) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
// Check whether there is token information
let token = localStorage.getItem('auth-token') | |' ';
if (token === ' ') {
this.router.navigate(['/login']);
return false;
}
// Determine whether the current connection can be accessed
let url: string = state.url;
if (token.indexOf('admin')! = =- 1 && url.indexOf('/crisis-center')! = =- 1) {
return true;
}
this.router.navigate(['/login']);
return false;
}
canActivateChild(
childRoute: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
let token = localStorage.getItem('auth-token') | |' ';
if (token === ' ') {
this.router.navigate(['/login']);
return false;
}
return token === 'admin-master'; }}Copy the code
Add a crisis-detail component to the Angular CLI as a child of crisis-List
ng g component crisis-detail
Copy the code
Next add a router-outlet tag to the crisis-list to define the render exit for the child routes
<h2>Crisis center</h2>
<ul class="crises">
<li *ngFor="let crisis of crisisList">
<a [routerLink] ="[crisis.id]">
<span class="badge">{{ crisis.id }}</span>{{ crisis.name }}
</a>
</li>
</ul>
<! Define render exits for child routes -->
<router-outlet></router-outlet>
Copy the code
In the authentication and authorization configuration for child routes, we can choose to add canActivateChild attribute for each child route, or we can define an empty address child route, and make all crisis-list child routes as the child routes of this empty route. By adding the canActivateChild property to this empty path, the daemon rules are applied to all child routes
This essentially changes the two-level routing mode (parent: crisis-list, child: crisis-detail) to three-level (parent: crisis-list, child: “(empty path), grandson: crisis-detail).
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
// Import components
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { CrisisDetailComponent } from './crisis-detail/crisis-detail.component';
// Import the route guard
import { AuthGuard } from './auth/auth.guard';
const routes: Routes = [
{
path: 'crisis-center',
component: CrisisListComponent,
canActivate: [AuthGuard], // Add a canActivate guard for the current route
children: [{
path: ' ',
canActivateChild: [AuthGuard], // Add a canActivate guard for child routes
children: [{
path: 'detail',
component: CrisisDetailComponent
}]
}]
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule { }
Copy the code
CanDeactivate: Processes uncommitted user changes
When performing operations such as form filling, because a submission action will be involved, it is better to pause when the user leaves without clicking the save button and give the user a friendly reminder to choose the following actions
Create a route guard that inherits from the CanDeactivate interface
ng g guard hero-list/guards/hero-can-deactivate
Copy the code
Unlike CanActivate and CanActivateChild above, for CanDeactivate guard, we need to replace unknown with the component we actually need to guard the route
import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class HeroCanDeactivateGuard implementsCanDeactivate<unknown> { canDeactivate( component: unknown, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState? : RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return true; }}Copy the code
For example, this is the HeroListComponent, so we need to change the generic parameter unknown to HeroListComponent. With the Component parameter, we can get information about the component that needs to be guarded
import { Injectable } from '@angular/core';
import {
CanDeactivate,
ActivatedRouteSnapshot,
RouterStateSnapshot,
UrlTree,
} from '@angular/router';
import { Observable } from 'rxjs';
// Import the component that needs to be route guarded
import { HeroListComponent } from '.. /hero-list.component';
@Injectable({
providedIn: 'root',})export class HeroCanDeactivateGuard
implementsCanDeactivate<HeroListComponent> { canDeactivate( component: HeroListComponent, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState? : RouterStateSnapshot ): | Observable<boolean | UrlTree>
| Promise<boolean | UrlTree>
| boolean
| UrlTree {
// Determine whether the original data has been modified
//
const data = component.hero;
if (data === undefined) {
return true;
}
const origin = component.heroList.find(hero= > hero.id === data.id);
if (data.name === origin.name) {
return true;
}
return window.confirm('Content not submitted, confirm to leave? '); }}Copy the code
Here, the simulation determines whether the user has modified the original data. When the user modifies the data and moves to another page, the routing guard is triggered to prompt the user whether to save the data and then leave the current page
Asynchronous routing
Lazy loading
As the application grows, using existing loading methods can cause the application to load all the components on the first visit, resulting in a slow first rendering of the system. So you can use lazy loading to load components only when specific modules are requested
Lazy loading only applies to ngModules, so to use lazy loading as a function point, we need to split the system into separate modules by function
Start by creating a crisis module through the Angular CLI.
Ng g module --help -- Create the crisis center module (automatically import the newly created CrisisModule in app.moudule. Ts and add the routing configuration for the current module) ng g module crisis --module app --routingCopy the code
Move the crisis-list and crisis-detail components under the Crisis module and add declarations for crisis-list and crisis-detail components to the CrisisModule. Also remove the component code originally declared in app.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CrisisRoutingModule } from './crisis-routing.module';
import { FormsModule } from '@angular/forms';
// Import the components used in the module
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { CrisisDetailComponent } from './crisis-detail/crisis-detail.component';
@NgModule({
declarations: [
CrisisListComponent,
CrisisDetailComponent
],
imports: [
CommonModule,
FormsModule,
CrisisRoutingModule
]
})
export class CrisisModule { }
Copy the code
Similarly, move the route configuration of the current module to the crisis-routing.module.ts special route configuration file and remove the relevant route configuration from app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
// Import components
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { CrisisDetailComponent } from './crisis-detail/crisis-detail.component';
// Import the route guard
import { AuthGuard } from '.. /auth/auth.guard';
const routes: Routes = [{
path: ' ',
component: CrisisListComponent,
canActivate: [AuthGuard], // Add a canActivate guard for the current route
children: [{
path: ' ',
canActivateChild: [AuthGuard], // Add a canActivate guard for child routes
children: [{
path: 'detail',
component: CrisisDetailComponent
}]
}]
}];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class CrisisRoutingModule { }
Copy the code
Re-run the project. If you set the module creation command to automatically import the current module into the app.module.ts file, you will most likely encounter the following problems
The problem here is similar to why configuring wildcard routing needs to be last, because scaffolding is added to the end of the array when it helps us import the module we created into app.module.ts. Meanwhile, since we have moved the crisis-routing.module.ts route configuration of the crisis module, the framework will pre-match the wildcard route set in app-routing.module.ts during route matching. This makes it impossible to find the component that actually corresponds to it, so we need to place the AppRoutingModule at the end of the declaration
When the problem is resolved, lazy loading can be set for the Crisis module
When configuring the lazy route, we need to configure it in a way similar to that of the child route, using the loadChildren attribute of the route to load the corresponding module instead of the specific component. The modified AppRoutingModule code is as follows
import { HeroCanDeactivateGuard } from './hero-list/guards/hero-can-deactivate.guard';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{
path: 'crisis-center',
loadChildren: (a)= > import('./crisis/crisis.module').then(m= > m.CrisisModule)
}
];
@NgModule({
imports: [RouterModule.forRoot(routes, { enableTracing: true })],
exports: [RouterModule],
})
export class AppRoutingModule { }
Copy the code
When navigating to the /crisis-center route, the framework dynamically loads the CrisisModule via the loadChildren string and then adds the CrisisModule to the current route configuration, with lazy loading and reconfiguration only happening once. That is, when the route is first requested, both the module and the route are immediately available on subsequent requests
CanLoad: Prevents the loading of components that are not authenticated or authorized
In the above code, we have used CanActivate and CanActivateChild route guards to authenticate and authorize the CrisisModule, but when we click on the link without permission to access the CrisisModule, the frame route will still load the module. To prevent the problem of loading modules without authorization, use the CanLoad guard
Because the judgment logic is the same as the authentication and authorization logic, we can inherit the CanLoad interface in AuthGuard. The modified AuthGuard code is as follows
import { Injectable } from '@angular/core';
import {
CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router, CanActivateChild, CanLoad, Route, UrlSegment
} from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate, CanActivateChild, CanLoad {
/** * ctor * @param router */
constructor(private router: Router) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
// Check whether there is token information
let token = localStorage.getItem('auth-token') | |' ';
if (token === ' ') {
this.router.navigate(['/login']);
return false;
}
// Determine whether the current connection can be accessed
let url: string = state.url;
if (token.indexOf('admin')! = =- 1 && url.indexOf('/crisis-center')! = =- 1) {
return true;
}
this.router.navigate(['/login']);
return false;
}
canActivateChild(
childRoute: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
let token = localStorage.getItem('auth-token') | |' ';
if (token === ' ') {
this.router.navigate(['/login']);
return false;
}
return token === 'admin-master';
}
canLoad(route: Route, segments: UrlSegment[]): boolean | Observable<boolean> | Promise<boolean> {
let token = localStorage.getItem('auth-token') | |' ';
if (token === ' ') {
this.router.navigate(['/login']);
return false;
}
let url = ` /${route.path}`;
if (token.indexOf('admin')! = =- 1 && url.indexOf('/crisis-center')! = =- 1) {
return true; }}}Copy the code
Similarly, after the implementation of the route guard is completed, add the route guard to be used to the canLoad array of the Crisis-Center route so that the module will not be loaded when authentication fails
import { HeroCanDeactivateGuard } from './hero-list/guards/hero-can-deactivate.guard';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{
path: 'crisis-center',
loadChildren: (a)= > import('./crisis/crisis.module').then(m= > m.CrisisModule)
}
];
@NgModule({
imports: [RouterModule.forRoot(routes, { enableTracing: true })],
exports: [RouterModule],
})
export class AppRoutingModule { }
Copy the code
Of the pit
Personal Profile: Born in 1996, born in a fourth-tier city in Anhui province, graduated from Top 10 million universities. .NET programmer, gunslinger, cat. It will begin in December 2016. NET programmer career, Microsoft. NET technology stalwart, aspired to be the cloud cat kid programming for Google the best. NET programmer. Personal blog: yuiter.com blog garden blog: www.cnblogs.com/danvic712