Angular(4)路由
1. 最基本的路由
1.1 API:
- Router
- ActivatedRoute
路由的基本使用
名称 | 简介 |
---|---|
Routes | 路由的配置,保存着哪一个URL对应展示哪一个组件和在哪一个RouterOutler展示 |
RouterOutler | 在HTML中标记路由内容呈现的占位符指令 |
Router | 运行时执行的路由对象,可以通过navigate()和navigateByUrl()方法来导航到指定路由 |
RouterLink | 在HTML中声明导航的指令 |
ActivatedRoute | 当前激活的路由对象,保存着当前路由信息,路由地址,路由参数 |
1.2 将路由添加到 app.modules
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HeroListComponent } from './router-study/heroes/hero-list/hero-list.component';
import { RouterModule, Routes } from '@angular/router';
import { CrisisListComponent } from './router-study/crisis-center/crisis-list/crisis-list.component';
import { NotFoundComponent } from './router-study/not-found/not-found.component';
const routes: Routes = [
{ path: 'crisis-center', component: CrisisListComponent },
{ path: 'heroes', component:HeroListComponent },
// { path: '', component:HeroListComponent },
{ path: '', redirectTo: '/heroes' ,pathMatch: 'full'},
// { path: '**', redirectTo: '/heroes' },
{ path: '**', component:NotFoundComponent },
];
@NgModule({
declarations: [
AppComponent,
HeroListComponent,
CrisisListComponent,
NotFoundComponent,
],
imports: [
BrowserModule,
RouterModule.forRoot(routes)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
app.components.html
<div class="app">
<h2>Router Study</h2>
<nav>
<a routerLink = "/crisis-center" routerLinkActive="act-link">crisis center</a>||
<a routerLink = "/heroes" routerLinkActive="act-link">heroes</a>
</nav>
<router-outlet></router-outlet>
</div>
知识点总结
创建路由
ng new routing-app --routing
路由配置
注意:path里面不要加 \ 线
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{ path: 'crisis-center', component: CrisisListComponent },
{ path: 'heroes', component:HeroListComponent },
// { path: '', component:HeroListComponent },
{ path: '', redirectTo: '/heroes' ,pathMatch: 'full'},
// { path: '**', redirectTo: '/heroes' },
{ path: '**', component:NotFoundComponent },
];
imports: [
RouterModule.forRoot(routes)
],
路由出口
<router-outlet></router-outlet>
路由跳转
<a routerLink = "/heroes">heroes</a>
路由高亮
<a routerLink = "/crisis-center" routerLinkActive="act-link">crisis center</a>
<a routerLink = "/heroes" routerLinkActive="act-link">heroes</a>
//==================================================================
.act-link{
color:red;
}
路由404
注意: 配置里面通配符代表没有的都往这里,注意这里不能写在前面,不然angualr的路由会优先匹配这里
{ path: '**', component:NotFoundComponent },
// { path: '**', redirectTo: '/heroes' },
路由为空-重定向
{ path: '', redirectTo: '/heroes' ,pathMatch: 'full'},
// { path: '', component:HeroListComponent },
路由模块
创建app路由模块
- app.routing.module.ts
ng new routing-app --routing
添加 AppRoutingModule
在 Angular 中,最好在一个独立的顶层模块中加载和配置路由器,它专注于路由功能,然后由根模块 AppModule
导入它。
按照惯例,这个模块类的名字叫做 AppRoutingModule,并且位于 src/app
下的 app-routing.module.ts 文件中。
使用 CLI 生成它。
ng generate module app-routing --flat --module=app
--flat 把这个文件放进了
src/app
中,而不是单独的目录中。--module=app 告诉 CLI 把它注册到 AppModule 的 imports 数组中。
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HeroListComponent } from './router-study/heroes/hero-list/hero-list.component';
import { CrisisListComponent } from './router-study/crisis-center/crisis-list/crisis-list.component';
import { NotFoundComponent } from './router-study/not-found/not-found.component';
import { AppRoutingModule } from './app-routing.module';
import { HeroesModule } from './router-study/heroes/heroes.module';
import { CrisisCenterModule } from './router-study/crisis-center/crisis-center.module';
@NgModule({
declarations: [
AppComponent,
HeroListComponent,
CrisisListComponent,
NotFoundComponent,
],
imports: [
BrowserModule,
CrisisCenterModule,
HeroesModule,
AppRoutingModule, //这个模块必须放在最后,否则会被通配符组件/模块 拦截
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
app.routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HeroListComponent } from './router-study/heroes/hero-list/hero-list.component';
import { CrisisListComponent } from './router-study/crisis-center/crisis-list/crisis-list.component';
import { NotFoundComponent } from './router-study/not-found/not-found.component';
const routes: Routes = [
{ path: 'crisis-center', component: CrisisListComponent },
{ path: 'heroes', component:HeroListComponent },
{ path: '', redirectTo: '/heroes' ,pathMatch: 'full'},
{ path: '**', component:NotFoundComponent },
];
@NgModule({
declarations: [], //这里不用声明组件
imports: [
RouterModule.forRoot(routes),
],
exports: [ RouterModule ] //记得导出路由模块
})
export class AppRoutingModule { }
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HeroListComponent } from './router-study/heroes/hero-list/hero-list.component';
import { CrisisListComponent } from './router-study/crisis-center/crisis-list/crisis-list.component';
import { NotFoundComponent } from './router-study/not-found/not-found.component';
import { AppRoutingModule } from './app-routing.module';
@NgModule({
declarations: [
AppComponent,
HeroListComponent,
CrisisListComponent,
NotFoundComponent,
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
app 路由模块再次拆分
app.routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { NotFoundComponent } from './router-study/not-found/not-found.component';
const routes: Routes = [
{ path: '', redirectTo: '/heroes' ,pathMatch: 'full'},
{ path: '**', component:NotFoundComponent },
];
@NgModule({
imports: [
RouterModule.forRoot(routes),
],
exports: [ RouterModule ]
})
export class AppRoutingModule { }
crisis-center-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { CrisisListComponent } from './crisis-list/crisis-list.component';
const routes: Routes = [
{ path: 'crisis-center', component: CrisisListComponent },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class CrisisCenterRoutingModule { }
heros-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HeroListComponent } from './hero-list/hero-list.component';
const routes: Routes = [
{ path: 'heroes', component:HeroListComponent },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class HeroesRoutingModule { }
补充:完成后需要把路由模块导入相应的管理模块,然后将管理模块导入app.module.ts根模块,形成树结构进行统一管理
路由导航
API : https://angular.cn/api/router/Router
声明式路由导航
- routerLink
<a routerLink = "/crisis-center" routerLinkActive="act-link">crisis center</a>||
<a routerLink = "/heroes" routerLinkActive="act-link">heroes</a>
//<a [routerLink]="['/crisis-center']" routerLinkActive="act-link" >crisis center</a>
//<a [routerLink]="['/heroes']" routerLinkActive="act-link" >heroes</a>
编程式路由导航
- navigateByUrl
- navigate
<a (click)="toCrisisCenter()" routerLinkActive="act-link">crisis center</a>||
<a (click)="toHeroes()" routerLinkActive="act-link">heroes</a>
export class AppComponent {
constructor(private router:Router){}
toCrisisCenter(){
this.router.navigateByUrl('/crisis-center');
// this.router.navigate( ['/crisis-center']);
}
toHeroes(){
this.router.navigateByUrl('/heroes');
// this.router.navigate( ['/heroes']);
}
}
onSameUrlNavigation
- onSameUrlNavigation: 'reload' | 'ignore'
定义当路由器收到一个导航到当前 URL 的请求时应该怎么做。可取下列值之一:
'ignore'
:路由器会忽略此请求。
'reload'
:路由器会重新加载当前 URL。用来实现"刷新"功能。
app.routeing.module.ts
//imports: [RouterModule.forRoot(routes)],
imports: [RouterModule.forRoot(routes,{
onSameUrlNavigation:'reload',
//enableTracing:true 用来测试,监测路由变化
})],
路由参数
params
传递产数到地址栏并跳转
- 当有 id 导航到详情页
const routes: Routes = [
{ path: 'heroes', component:HeroListComponent },
{ path: 'hero/:id', component:HeroDetailComponent },
];
- 获取当前点击的 id ,并提交到地址栏,经由上面的路由跳转到详情页
constructor(private router:Router){}
onSelect(id:number){
this.selectedId = id;
//this.router.navigateByUrl('/hero/' + id); 或者
this.router.navigate( ['/heroes', id]);
//this.router.navigate( ['/heroes', id, 'aaa']);
}
[routerLink]="['/hero', hero.id]">{{ hero.name }}</li>
提取地址栏 id
- 提取当前地址栏 id ,然后根据id进入当前id的英雄详情
hero-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.scss']
})
export class HeroDetailComponent implements OnInit {
constructor(
private router: Router,
private route: ActivatedRoute,
private heroService: HeroService
) { }
// ngOnInit(): void {
// this.route.paramMap.subscribe(params =>{
// console.log("params",params.get('id'));
// this.heroService.getHero(params.get('id')).subscribe(hero=>{
// console.log('hero',hero);
// })
// })
// }
hero$: Observable<Hero>;
ngOnInit(): void { //返回 id对应的英雄数据
this.hero$ = this.route.paramMap.pipe(switchMap(params => {
return this.heroService.getHero(params.get('id'));
})) ;
}
back(id:number){
this.router.navigate(['/heroes',{id}])
}
}
- 显示英雄详情
<div *ngIf="hero$ | async as hero">
<h2>英雄详情</h2>
<h3>"{{ hero.name }}"</h3>
<div>
<label>Id: </label>{{ hero.id }}</div>
<div>
<label>Name: </label>
<input [(ngModel)]="hero.name" placeholder="name"/>
</div>
<p>
//返回
//<button (click)="back(hero.id)" class="btn btn-secondary">Back to Heroes</button>
<button [routerLink]="['/heroes', { id: hero.id }]" class="btn btn-secondary">Back to Heroes</button>
</p>
</div>
过滤出 id 对应的英雄数据
- 服务里有两个方法
- getHeroes( ) 用来获取英雄列表数据
- getHero( ) 用来从英雄列表里搜索当前 id,并返回为当前 id 的英雄数据
heroes/hero.service.ts
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
import {map} from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class HeroService {
constructor() { }
getHeroes(): Observable<Hero[]> {
return of(HEROES);
}
getHero(id: number | string) {
return this.getHeroes().pipe(
map((heroes: Hero[]) => heroes.find(hero => hero.id === +id))
);
}
}
hero-list/hero-list.components.ts
@Component({
selector: 'app-hero-list',
templateUrl: './hero-list.component.html',
styles: [
`
.list-group {width: 280px;cursor: pointer;}
`
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeroListComponent implements OnInit {
heroes$: Observable<Hero[]>;
selectedId: number;
constructor(private heroServe: HeroService, private router: Router, private route: ActivatedRoute) { }
/*ngOnInit(): void {
// this.heroes$ = this.heroServe.getHeroes();
this.heroes$ = this.route.paramMap.pipe(
switchMap(params => {
console.log('params list', params);
this.selectedId = +params.get('id');
return this.heroServe.getHeroes();
})
);
}*/
ngOnInit(): void {
this.selectedId = +this.route.snapshot.paramMap.gest('id');
this.heroes$ = this.heroServe.getHeroes();
// this.getQueryName();
}
private getQueryName() {
console.log(this.route.snapshot.queryParamMap.get('name'));
/* this.route.queryParamMap.subscribe(res => {
console.log(res.get('name'));
});*/
}
onSelect(id: number) {
// this.selectedId = id;
// this.router.navigateByUrl('/hero/' + id);
this.router.navigate(['/hero', id]);
}
}
点击返回后依然显示高亮
- 数据显示
- 再次传参到 地址栏
<div *ngIf="hero$ | async as hero">
<h2>英雄详情</h2>
<h3>"{{ hero.name }}"</h3>
<div>
<label>Id: </label>{{ hero.id }}</div>
<div>
<label>Name: </label>
<input [(ngModel)]="hero.name" placeholder="name"/>
</div>
<p>
<button (click)="back(hero.id)" class="btn btn-secondary">
Back to Heroes
</button>
// back(id:number){
// this.router.navigate(['/heroes',{id}]) {id:id}的简写
// }
<button [routerLink]="['/heroes', { id: hero.id }]" class="btn btn-secondary">
Back to Heroes
</button>
</p>
</div>
- 再次拿到地址栏 id
- 根据 id 显示对应的高亮
export class HeroListComponent implements OnInit {
heroes$: Observable<Hero[]>;
selectedId:number;
constructor(private route: ActivatedRoute, private heroService: HeroService, private router:Router) { }
ngOnInit(): void {
// this.heroes$ = this.heroService.getHeroes();
this.heroes$ = this.route.paramMap.pipe(
switchMap(params => {
console.log('params',params)
this.selectedId = +params.get('id');
return this.heroService.getHeroes()
})
);
}
onSelect(id:number){
// this.selectedId = id;
this.router.navigateByUrl('/hero/' + id);
}
}
query(?后参数)
paramMap
// https://www.xxxx?name=jack
//this.route.queryParamMap.subscribe(res =>{
// console.log(res.get('name'))
//})
this.route.snapshot.queryParamMap.get('name')
queryParamMap
ngOnInit() {
this.selectedId = +this.route.snapshot.paramMap.get('id');
// +转数字类型
this.heroes$ = this.heroService.getHeroes();
// this.getQueryName()
}
//getQueryName(){
// this.route.queryParamMap.subscribe(res=>{
// console.log('res',res)
// console.log( res.get('name'))
// })
等价于
//console.log(this.route.snapshot.queryParamMap.get('name'))
//}
snapshot
ngOnInit(): void{
this.selectedId = +this.route.snapshot.paramMap.get('id');
this.heroes$ = this.heroServe.getHeroes();
}
嵌套路由和相对导航
嵌套路由
const crisisCenterRoutes: Routes = [
{
path: 'crisis-center',
component: CrisisCenterComponent,
children: [
{
path: '',
component: CrisisListComponent,
children: [
{
path: ':id',
component: CrisisDetailComponent
},
{
path: '',
component: CrisisCenterHomeComponent
}
]
}
]
}
];
s
<div class="crisis-certer">
<h2>危机中心</h2>
<router-outlet></router-outlet>
//记得写插槽
</div>
相对导航
//导航到上一级
<li><a routerLink="../">Relative Route to second component</a></li>
//
<li><a routerLink="../second-component">Relative Route to second component</a></li>
<li><a routerLink="../second-component">Relative Route to second component</a></li>
goToItems() {
this.router.navigate(['items'], { relativeTo: this.route });
}
多重路由(具名路由)
//<div [@routeAnimation]="getAnimationData(routerOutlet)">
// <router-outlet #routerOutlet="outlet"></router-outlet>
//</div>
<router-outlet name="popup"></router-outlet>
outlet
{
path: 'compose',
component: ComposeMessageComponent,
outlet: 'popup'
},
点击跳到 ComposeMessageComponent 组件
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
导航到危机中心并点击“Contact”,你将会在浏览器的地址栏看到如下 URL:
http://.../crisis-center(popup:compose)
清除第二路由
closePopup() {
// Providing a `null` value to the named outlet
// clears the contents of the named outlet
this.router.navigate([{ outlets: { popup: null }}]);
}
路由守卫
路由器可以支持多种守卫接口:
- 用
CanActivate
来处理导航到某路由的情况。 - 用
CanActivateChild
来处理导航到某子路由的情况。 - 用
CanDeactivate
来处理从当前路由离开的情况. - 用
Resolve
在路由激活之前获取路由数据。 - 用
CanLoad
来处理异步导航到某特性模块的情况。
CanActivate
最简单的CanActivate
- 只让登录用户进入产品信息路由。
新建guard目录。目录下新建login.guard.ts。
//LoginGuard类实现CanActivate接口,返回true或false,Angular根据返回值判断请求通过或不通过。
import { CanActivate } from "@angular/router";
export class LoginGuard implements CanActivate{
canActivate(){
let loggedIn :boolean= Math.random()<0.5;
if(!loggedIn){
console.log("用户未登录");
}
return loggedIn;
}
}
配置product路由。先把LoginGuard加入providers,在指定路由守卫。
canActivate可以指定多个守卫,值是一个数组。
const routes: Routes = [
{ path: '', redirectTo : 'home',pathMatch:'full' },
{ path: 'chat', component: ChatComponent, outlet: "aux"},//辅助路由
{ path: 'home', component: HomeComponent },
{ path: 'product/:id', component: ProductComponent, children:[
{ path: '', component : ProductDescComponent },
{ path: 'seller/:id', component : SellerInfoComponent }
] ,canActivate: [LoginGuard]},
{ path: '**', component: Code404Component }
];
登录功能模拟
<h1 class="title">Angular Router</h1>
<nav>
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
//
<a routerLink="/admin" routerLinkActive="active">Admin</a>
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
</nav>
<div [@routeAnimation]="getAnimationData(routerOutlet)">
<router-outlet #routerOutlet="outlet"></router-outlet>
</div>js
<router-outlet name="popup"></router-outlet>
src/app/admin/admin-routing.module.ts
import { AuthGuard } from '../auth/auth.guard';
const adminRoutes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard], //路由导航到admin时触发canActivate验证
children: [
{
path: '',
children: [
{ path: 'crises', component: ManageCrisesComponent },
{ path: 'heroes', component: ManageHeroesComponent },
{ path: '', component: AdminDashboardComponent }
],
}
]
}
];
@NgModule({
imports: [
RouterModule.forChild(adminRoutes)
],
exports: [
RouterModule
]
})
export class AdminRoutingModule {}
添加验证
src/app/auth/auth.service.ts (excerpt)
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { tap, delay } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class AuthService {
isLoggedIn = false;
//存储URL以便我们可以在登录后重定向
redirectUrl: string;
//点击登录,将isLogedIn,一秒后改为true
login(): Observable<boolean> {
return of(true).pipe(
delay(1000),
tap(val => this.isLoggedIn = true)
);
}
//点击退出,将isLoggedIn改为false
logout(): void {
this.isLoggedIn = false;
}
}
src/app/auth/auth.guard.ts (v2)
- 这个
ActivatedRouteSnapshot
包含了即将被激活的路由, - 而
RouterStateSnapshot
包含了该应用即将到达的状态。 你应该通过守卫进行检查。 - 如果用户还没有登录,你就会用 RouterStateSnapshot.url 保存用户来自的 URL 并让路由器跳转到登录页(你尚未创建该页)。 这间接导致路由器自动中止了这次导航,checkLogin() 返回 false 并不是必须的,但这样可以更清楚的表达意图。
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, UrlTree } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): true | UrlTree {
let url: string = state.url; //admin,被canActivate阻止进入的路由
return this.checkLogin(url); //如果isLoggedIn为true,直接进入,反之跳到login登录页
}
//返回true,或者一个地址(UrlTree)
checkLogin(url: string): true | UrlTree {
if (this.authService.isLoggedIn) { return true; }
this.authService.redirectUrl = url; //登录成功后重定向到admin
return this.router.parseUrl('/login');
}
}
src/app/auth/login/login.html
<h2>LOGIN</h2>
<p>{{message}}</p>
<p>
<button (click)="login()" *ngIf="!authService.isLoggedIn">Login</button>
<button (click)="logout()" *ngIf="authService.isLoggedIn">Logout</button>
</p>
src/app/auth/login/login.ts
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import {AuthService} from '../auth.service';
import {Router} from '@angular/router';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styles: [
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class LoginComponent {
message: string;
constructor(public authService: AuthService, public router: Router) {
this.setMessage();
}
setMessage() {
this.message = 'Logged ' + (this.authService.isLoggedIn ? 'in' : 'out');
}
login() {
this.message = 'Trying to log in ...';
this.authService.login().subscribe(() => {
this.setMessage();
if (this.authService.isLoggedIn) {
const redirectUrl = this.authService.redirectUrl;
// Redirect the user
this.router.navigate([redirectUrl]);
}
});
}
logout() {
this.authService.logout();
this.setMessage();
}
}
CanActivateChild
import { Injectable } from '@angular/core';
import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, UrlTree, CanActivateChild} from '@angular/router';
import {AuthService} from './auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate, CanActivateChild {
constructor(private authService: AuthService, private router: Router) {}
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): true | UrlTree {
const url = state.url;
console.log('canActivate', url);
return this.checkLogin(url);
}
canActivateChild(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): true | UrlTree {
console.log('canActivateChild');
return this.canActivate(route, state);
}
checkLogin(url: string): true | UrlTree {
if (this.authService.isLoggedIn) { return true; }
// Store the attempted URL for redirecting
this.authService.redirectUrl = url;
// Redirect to the login page
return this.router.parseUrl('/login');
}
}
这个 AuthGuard
添加到“无组件的”管理路由,来同时保护它的所有子路由,而不是为每个路由单独添加这个 AuthGuard
。
admin/admin-routing.modul.ts
const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard],
children: [
{
path: '',
canActivateChild: [AuthGuard],
children: [
{ path: 'crises', component: ManageCrisesComponent },
{ path: 'heroes', component: ManageHeroesComponent },
{ path: '', component: AdminDashboardComponent }
]
}
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AdminRoutingModule { }
CanDeactivate
crisis-center-routing.module.ts
const routes: Routes = [
{
path: 'crisis-center',
component: CrisisCenterComponent,
children: [
{
path: 'list',
component: CrisisListComponent,
children: [
{
path: ':id',
component: CrisisDetailComponent,
canDeactivate: [CanDeactivateGuard] //从详情页离开时,激活判断
},
{
path: '',
component: CrisisCenterHomeComponent
}
]
},
{
path: '',
redirectTo: 'list',
pathMatch: 'full'
}
]
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class CrisisCenterRoutingModule { }
can-deactivate.guard.ts
import { Injectable } from '@angular/core';
import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, CanDeactivate} from '@angular/router';
import {CrisisDetailComponent} from './crisis-detail/crisis-detail.component';
@Injectable({
providedIn: 'root'
})
export class CanDeactivateGuard implements CanDeactivate<CrisisDetailComponent> {
canDeactivate(
component: CrisisDetailComponent,
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
) {
console.log('url', state.url);
return component?.canDeactivate();
//守卫不需要知道哪个组件有 deactivate 方法,
//它可以检测 CrisisDetailComponent 组件有没有 canDeactivate() 方法并调用它。
//守卫在不知道任何组件 deactivate 方法细节的情况下,就能让这个守卫重复使用。
}
}
crisis-detail.component.html
<div *ngIf="crisis">
<h5>英雄详情</h5>
<h2>{{crisis.name | uppercase}} Details</h2>
<div><span>id: </span>{{crisis.id}}</div>
<div>
<label>name:
<input [(ngModel)]="crisis.name" placeholder="name" />
</label>
</div>
<div class="btns">
<button class="btn btn-secondary" (click)="cancel()">取消</button>
<button class="btn btn-secondary" (click)="save()">保存</button>
</div>
</div>
crisis-detail.component.ts
@Component({
selector: 'app-crisis-detail',
templateUrl: './crisis-detail.component.html',
styles: [
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CrisisDetailComponent implements OnInit {
editName = '';
crisis: Crisis;
constructor(
private router: Router,
private route: ActivatedRoute,
private crisisServe: CrisisService,
private cdr: ChangeDetectorRef,
private dialogServe: DialogService) { }
ngOnInit(): void {
this.route.paramMap.pipe(switchMap(params => {
return this.crisisServe.getCrisis(params.get('id'));
})).subscribe(crisis => {
// console.log('cc');
this.crisis = crisis;
this.editName = crisis.name;
this.cdr.markForCheck();
});
}
cancel() {
this.gotoCrises();
}
save() {
this.editName = this.crisis.name;
this.gotoCrises();
}
gotoCrises() {
this.router.navigate(['../'], { relativeTo: this.route });
}
canDeactivate() {
if (!this.crisis || this.crisis.name === this.editName) {
return true;
}
return this.dialogServe.confirm('Discard changes?');
}
}
dialog.service.ts
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class DialogService {
/**
* Ask user to confirm an action. `message` explains the action and choices.
* Returns observable resolving to `true`=confirm or `false`=cancel
*/
confirm(message?: string): Observable<boolean> {
const confirmation = confirm(message || 'Is it OK?');
return of(confirmation);
}
}
Resolve
进入组件前获取数据
crisis-detail-resolver.service.ts
import { Injectable } from '@angular/core';
import {ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot} from '@angular/router';
import {Crisis} from './crisis';
import {EMPTY, Observable, of} from 'rxjs';
import {CrisisService} from './crisis.service';
import {first, mergeMap} from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class CrisisDetailResolverService implements Resolve<Crisis>{
constructor(private crisisServe: CrisisService, private router: Router) { }
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Crisis | never> {
return this.crisisServe.getCrisis(route.paramMap.get('id')).pipe(
first(), //转换为一个有限的流
mergeMap(crisis => {
if (crisis) {
console.log('找到了crisis');
return of(crisis);
} else { // 没找到具体的Crisis
this.router.navigate(['/crisis-center']);
return EMPTY; //返回empty则取消导航
}
})
);
}
}
crisis.service.ts
import {Observable, of} from 'rxjs';
import { Injectable } from '@angular/core';
import {CRISES} from './mock-crises';
import {Crisis} from './crisis';
import {map} from 'rxjs/operators';
import {Hero} from '../heroes/hero';
@Injectable({
providedIn: 'root',
})
export class CrisisService {
constructor() { }
getCrises(): Observable<Crisis[]> {
return of(CRISES);
}
getCrisis(id: number | string): Observable<Crisis> {
return this.getCrises().pipe(
map((crises: Crisis[]) => crises.find(crisis => crisis.id === +id))
);
}
}
crisis-center-routing.module.ts
const routes: Routes = [
{
path: '',
component: CrisisCenterComponent,
children: [
{
path: 'list',
component: CrisisListComponent,
children: [
{
path: ':id',
component: CrisisDetailComponent,
canDeactivate: [CanDeactivateGuard],
resolve: {
crisis: CrisisDetailResolverService
},
data: {
name: '张三'
}
},
{
path: '',
component: CrisisCenterHomeComponent
}
]
},
{
path: '',
redirectTo: 'list',
pathMatch: 'full'
}
]
},
];
crisis-detail.component.ts
export class CrisisDetailComponent implements OnInit {
editName = '';
crisis: Crisis;
constructor(
private router: Router,
private route: ActivatedRoute,
private crisisServe: CrisisService,
private cdr: ChangeDetectorRef,
private dialogServe: DialogService) { }
ngOnInit(): void {
this.route.data.subscribe((data: { crisis: Crisis }) => {
console.log('data', data);
this.crisis = data.crisis;
this.editName = data.crisis.name;
this.cdr.markForCheck();
});
}
cancel() {
this.gotoCrises();
}
save() {
this.editName = this.crisis.name;
this.gotoCrises();
}
gotoCrises() {
this.router.navigate(['../'], { relativeTo: this.route });
}
canDeactivate() {
if (!this.crisis || this.crisis.name === this.editName) {
return true;
}
return this.dialogServe.confirm('Discard changes?');
}
}
查询参数及片段
checkLogin(url: string): boolean {
if (this.authService.isLoggedIn) { return true; }
// Store the attempted URL for redirecting
this.authService.redirectUrl = url;
// Redirect to the login page
return this.router.createUrlTree(['/login'], {
queryParams: { session_id: 1234567, name: '张三' },
fragment: 'anchor'
});
return false;
}
login() {
this.message = 'Trying to log in ...';
this.authService.login().subscribe(() => {
this.setMessage();
if (this.authService.isLoggedIn) {
const redirectUrl = this.authService.redirectUrl;
// Redirect the user
this.router.navigate([redirectUrl], {
//登录成功后是否保留查询参数和片段
queryParamsHandling: 'preserve',
preserveFragment: true
});
}
});
}
checkLogin(url: string): true|UrlTree {
if (this.authService.isLoggedIn) { return true; }
// Store the attempted URL for redirecting
this.authService.redirectUrl = url;
// Create a dummy session id
let sessionId = 123456789;
//跳转同时在地址栏添加
let navigationExtras: NavigationExtras = {
queryParams: { 'session_id': sessionId }, //查询参数,?后面内容
fragment: 'anchor' //片段,#后面的内容
queryParamsHandling:'preserve',
preeservFragment:true
};
// Redirect to the login page with extras
return this.router.createUrlTree(['/login'], navigationExtras);
}
}
方法2
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {Observable} from 'rxjs';
import {map} from 'rxjs/operators';
import {SelectivePreloadingStrategyService} from '../../../services/selective-preloading-strategy.service';
@Component({
selector: 'app-admin-dashboard',
template: `
<div>
admin-dashboard works!
<p>Session Id: {{ sessionId | async }}</p>
<hr>
<p>Token: {{ token | async }}</p>
<p>
PreLoad paths: {{ modules }}
</p>
</div>
`,
styles: [
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AdminDashboardComponent implements OnInit {
sessionId: Observable<string>;
token: Observable<string>;
modules: string[] = [];
constructor(private route: ActivatedRoute, preloadStrategy: SelectivePreloadingStrategyService) {
this.modules = preloadStrategy.preloadedModules;
}
ngOnInit() {
// Capture the session ID if available
this.sessionId = this.route
.queryParamMap
.pipe(map(params => params.get('session_id') || 'None'));
// Capture the fragment if available
this.token = this.route
.fragment
.pipe(map(fragment => fragment || 'None'));
}
}
路由懒加载
ES6 动态加载(懒加载)
标准导入
<button (clicl)=alertResult(2,5,3) ><button>
import {add} from './number'
alertResult(...args: number[]) {
alert(add(args));
}
number.ts
export function add(numbers: number[]) {
return numbers.reduce((prev, curr) => prev + curr, 0);
}
动态导入(懒加载)
<button (clicl)=alertResult(2,5,3) ><button>
alertResult(...args: number[]) {
// import('./number').then(({ default: d, add }) => {
// console.log('d', d);
// alert(add(args));
// });
import('./number').then(( module) => {
// console.log(' module', module);
alert(module.add(args));
});
}
number.ts
export function add(numbers: number[]) {
return numbers.reduce((prev, curr) => prev + curr, 0);
}
//export default ['a', 'b'];
路由懒加载
懒加载
app-routing.module.ts (load children)
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
},
最后一步是把管理特性区从主应用中完全分离开。 根模块 AppModule
既不能加载也不能引用 AdminModule
及其文件。
在 app.module.ts
中,从顶部移除 AdminModule
的导入语句,并且从 NgModule 的 imports
数组中移除 AdminModule
。
const routes: Routes = [
{ path: 'login', loadChildren: () => import('./pages/login/login.module').then(m => m.LoginModule) },
{ path: '', redirectTo: '/home/heroes', pathMatch: 'full' },
{ path: '**', redirectTo: '/home/heroes' }
];
CanLoad
- 保护对特性模块的未授权加载
src/app/auth/auth.guard.ts (CanLoad guard)
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate, CanActivateChild, CanLoad {
constructor(private authService: AuthService, private router: Router) {}
canLoad(route: Route, segments: UrlSegment[]): boolean {
const url = `${route.path}`;
console.log('canload url', url);
console.log('canload segments', segments);
return this.checkLogin(url);
}
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const url = state.url;
console.log('canActivate', url);
return this.checkLogin(url);
}
canActivateChild(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
console.log('canActivateChild');
return this.canActivate(route, state);
}
checkLogin(url: string): boolean {
if (this.authService.isLoggedIn) { return true; }
// Store the attempted URL for redirecting
this.authService.redirectUrl = url;
// Redirect to the login page
this.router.navigate(['/login'], {
queryParams: { session_id: 1234567, name: '张三' },
fragment: 'anchor'
});
return false;
}
}
c
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
canLoad: [AuthGuard] //返回true则加载
},
预加载
Router 提供了两种预加载策略:
- 完全不预加载,这是默认值。惰性加载的特性区仍然会按需加载。
- 预加载所有惰性加载的特性区。
c
RouterModule.forRoot(
appRoutes,
{
enableTracing: true, // <-- debugging purposes only
preloadingStrategy: PreloadAllModules
}
)
@NgModule({
imports: [RouterModule.forRoot(routes,{
//enableTracing: true,
preloadingStrategy: PreloadAllModules
}),
],
exports: [RouterModule]
})
自定义预加载策略
src/app/app-routing.module.ts (route data preload)
{
path: 'crisis-center',
loadChildren: () => import('./crisis-center/crisis-center.module').then(m => m.CrisisCenterModule),
data: { preload: true } //只加载preload为true的模块
},
路由器事件
Router
在每次导航过程中都会通过 Router.events
属性发出导航事件。这些事件的范围贯穿从导航开始和结束之间的多个时间点。导航事件的完整列表如下表所示。
路由事件 | 说明 |
---|---|
NavigationStart |
导航开始时触发的事件。 |
RouteConfigLoadStart |
在 Router 惰性加载路由配置之前触发的事件。 |
RouteConfigLoadEnd |
在某个路由已经惰性加载完毕时触发的事件。 |
RoutesRecognized |
当路由器解析了 URL,而且路由已经识别完毕时触发的事件。 |
GuardsCheckStart |
当路由器开始进入路由守卫阶段时触发的事件。 |
ChildActivationStart |
当路由器开始激活某路由的子路由时触发的事件。 |
ActivationStart |
当路由器开始激活某个路由时触发的事件。 |
GuardsCheckEnd |
当路由器成功结束了路由守卫阶段时触发的事件。 |
ResolveStart |
当路由器开始路由解析阶段时触发的事件。 |
ResolveEnd |
当路由器的路由解析阶段成功完成时触发的事件。 |
ChildActivationEnd |
当路由器成功激活某路由的子路由时触发的事件。 |
ActivationEnd |
当路由器成功激活了某个路由时触发的事件。 |
NavigationEnd |
当导航成功结束时触发的事件。 |
NavigationCancel |
当导航被取消时触发的事件。 这可能在导航期间某个路由守卫返回了 false 或返回了 UrlTree 以进行重定向时发生。 |
NavigationError |
当导航由于非预期的错误而失败时触发的事件。 |
Scroll |
用来表示滚动的事件。 |
当启用了 enableTracing
选项时,Angular 会把这些事件都记录到控制台。有关筛选路由器导航事件的示例,请参阅 Angular 中的 Observables 一章的路由器部分。
hash路由
- history 路由导航
- hash 模式导航
localhost:3000/crisis-center/
localhost:3000/src/#crisis-center/ 前面固定,#后面改变
使用:在app-routing.module.ts中添加useHash:true
@NgModule({
imports: [RouterModule.forRoot(routes.concat(namedRoutes), {
// enableTracing: true,
//preloadingStrategy: SelectivePreloadingStrategyService
useHash: true
})],
exports: [RouterModule]
})
查询参数
设置查询参数
假设发送 Get 请求时,需要设置对应的查询参数,预期的 URL 地址如下:
https://angular-http-guide.firebaseio.com/courses.json?orderBy="$key"&limitToFirst=1
创建 HttpParams 对象
import {HttpParams} from "@angular/common/http";
const params = new HttpParams()
.set('orderBy', '"$key"')
.set('limitToFirst', "1");
this.courses$ = this.http
.get("/courses.json", {params})
.do(console.log)
.map(data => _.values(data))
需要注意的是,我们通过链式语法调用 set()
方法,构建 HttpParams
对象。这是因为 HttpParams
对象是不可变的,通过 set()
方法可以防止该对象被修改。
每当调用 set()
方法,将会返回包含新值的 HttpParams
对象,因此如果使用下面的方式,将不能正确的设置参数。
const params = new HttpParams();
params.set('orderBy', '"$key"')
params.set('limitToFirst', "1");
等效方式
const params = new HttpParams({
fromString: 'orderBy="$key"&limitToFirst=1'
});