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'
});
posted @ 2020-12-04 17:33  Daeeman  阅读(185)  评论(0编辑  收藏  举报