Angular路由深入讲解

也许这是我最后一次写关于Angular的文章,但也不一定,如果有机会我可能会继续吧,但今天的主题很明确就是路由。

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    

{{title}}

`, styleUrls: ['./app.component.css'], }) export class AppComponent { title = 'Tour of Heroes'; }

这是主页面模版,通过routerLink实现定位,routerLinkActive会给当前显示的打上标记。

nav a.active {
  color: #039be5;
}

实际上则是对当前路由页面的填充。

那么问题来了,我们怎么知道每一个routerLink所指向的页面,看这个:


import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { DashboardComponent }   from './dashboard.component';
import { HeroesComponent }      from './heroes.component';
import { HeroDetailComponent }  from './hero-detail.component';

const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: 'dashboard',  component: DashboardComponent },
  { path: 'detail/:id', component: HeroDetailComponent },
  { path: 'heroes',     component: HeroesComponent }
];

@NgModule({
  imports: [ RouterModule.forRoot(routes) ],
  exports: [ RouterModule ]
})
export class AppRoutingModule {}

说以下三点:

  • 注意命名的规范,路由模块都以routing这种形式,.module结尾。
  • 路由里面的依赖组件必须都引进来。
  • 结尾处一定要 将这个路由注册[ RouterModule.forRoot(routes) ]。

redirectTo是进来之后重定向到dashboard这个组件显示的页面。重定向路由需要一个pathMatch属性,来告诉路由器如何用URL去匹配路由的路径,否则路由器就会报错。 在本应用中,路由器应该只有在完整的URL等于''时才选择HeroListComponent组件,因此我们要把pathMatch设置为'full'。

我们觉得有一点很奇怪{ path: 'detail/:id', component: HeroDetailComponent },这里出处在哪里,模版中并没有呀。。
此时我们定位到了heroes的组件:


import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

import { Hero } from './hero';
import { HeroService } from './hero.service';

@Component({
  selector: 'my-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: [ './heroes.component.css' ]
})
export class HeroesComponent implements OnInit {
  heroes: Hero[];
  selectedHero: Hero;

  constructor(
    private router: Router,
    private heroService: HeroService) { }

  getHeroes(): void {
    this.heroService.getHeroes().then(heroes => this.heroes = heroes);
  }

  ngOnInit(): void {
    this.getHeroes();
  }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }

  gotoDetail(): void {
    this.router.navigate(['/detail', this.selectedHero.id]);
  }
}

我们看这个

this.router.navigate(['/detail', this.selectedHero.id]);

这实际上是在走这个路由{ path: 'detail/:this.selectedHero.id', component: HeroDetailComponent }
我们来看模版的代码:

My Heroes

  • {{hero.id}} {{hero.name}}

{{selectedHero.name | uppercase}} is my hero

我们这里有一个选定的hero,点击之后就会走那个detail路由。问题来了,这样的话如何把需要的数据带到那个页面去呢?

import { Component, OnInit }        from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Location }                 from '@angular/common';

import { Hero }         from './hero';
import { HeroService }  from './hero.service';
@Component({
  selector: 'hero-detail',
  templateUrl: './hero-detail.component.html',
  styleUrls: [ './hero-detail.component.css' ]
})
export class HeroDetailComponent implements OnInit {
  hero: Hero;

  constructor(
    private heroService: HeroService,
    private route: ActivatedRoute,
    private location: Location
  ) {}

  ngOnInit(): void {
    this.route.paramMap
      .switchMap((params: ParamMap) => this.heroService.getHero(+params.get('id')))
      .subscribe(hero => this.hero = hero);
  }

  // 返回上一页
  goBack(): void {
    this.location.back();
  }
}

我们在ngOninit的时候,通过paramMap获取到来这个路由携带的参数,进而在服务中去做操作,把hero取出来。params.get('id')就是从hero页面传过来的selectedHero的id。

paramMap
An Observable
that contains a map of the required and optional parameters specific to the route. The map supports retrieving single and multiple values from the same parameter.

注意ActivatedRoute很关键,它可以帮我们获取路由的信息。

好了就剩最后一步就完成了路由的所有配置,那到底是什么呢?

import { AppRoutingModule }     from './app-routing.module';
@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule
  ]

以上截取部分app.module的代码。

我们有个页面是英雄列表,理出每条英雄明细,现在需要每个跳转到详细页面。


我们在routerlink里面也可以传递参数,这样也会走activeroute的路线。

我们再来看一个复杂一点的:


import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    

Angular Router

` }) export class AppComponent { }

我们这里有2个点不太明白,我也是一样,后面我们再解释。


import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ComposeMessageComponent }  from './compose-message.component';
import { PageNotFoundComponent }    from './not-found.component';
import { CanDeactivateGuard }       from './can-deactivate-guard.service';
import { AuthGuard }                from './auth-guard.service';
import { SelectivePreloadingStrategy } from './selective-preloading-strategy';

const appRoutes: Routes = [
  {
    path: 'compose',
    component: ComposeMessageComponent,
    outlet: 'popup'
  },
  {
    path: 'admin',
    loadChildren: 'app/admin/admin.module#AdminModule',
    canLoad: [AuthGuard]
  },
  {
    path: 'crisis-center',
    loadChildren: 'app/crisis-center/crisis-center.module#CrisisCenterModule',
    data: { preload: true }
  },
  { path: '',   redirectTo: '/heroes', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [
    RouterModule.forRoot(
      appRoutes,
      {
        enableTracing: true, // <-- debugging purposes only
        preloadingStrategy: SelectivePreloadingStrategy,

      }
    )
  ],
  exports: [
    RouterModule
  ],
  providers: [
    CanDeactivateGuard,
    SelectivePreloadingStrategy
  ]
})
export class AppRoutingModule { }

讲解一下几点:

  • { path: '**', component: PageNotFoundComponent } 找不到匹配,notfound组件
  • loadChildren: 'app/crisis-center/crisis-center.module#CrisisCenterModule',这个路由还有子路由,admin也是。

是不是有点傻傻分不清了,没关系咱们先看admin目录。

Angular路由深入讲解_第1张图片

接下来看


import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { AdminComponent }           from './admin.component';
import { AdminDashboardComponent }  from './admin-dashboard.component';
import { ManageCrisesComponent }    from './manage-crises.component';
import { ManageHeroesComponent }    from './manage-heroes.component';

import { AuthGuard }     from '../auth-guard.service';

const adminRoutes: Routes = [
  {
    path: '',
    component: AdminComponent,
    canActivate: [AuthGuard],
    children: [
      {
        path: '',
        canActivateChild: [AuthGuard],
        children: [
          { path: 'crises', component: ManageCrisesComponent },
          { path: 'heroes', component: ManageHeroesComponent },
          { path: '', component: AdminDashboardComponent }
        ]
      }
    ]
  }
];

@NgModule({
  imports: [
    RouterModule.forChild(adminRoutes)
  ],
  exports: [
    RouterModule
  ]
})
export class AdminRoutingModule {}

试想我们在主页面点击admin,他会怎么走,会走到admin子路由下面,此时对应的是path:'',canActivateChild: [AuthGuard]是什么鬼。

import { Injectable }       from '@angular/core';
import {
  CanActivate, Router,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  CanActivateChild,
  NavigationExtras,
  CanLoad, Route
}                           from '@angular/router';
import { AuthService }      from './auth.service';

@Injectable()
export class AuthGuard implements CanActivate, CanActivateChild, CanLoad {
  constructor(private authService: AuthService, private router: Router) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    let url: string = state.url;

    return this.checkLogin(url);
  }

  canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    return this.canActivate(route, state);
  }

  canLoad(route: Route): boolean {
    let url = `/${route.path}`;

    return this.checkLogin(url);
  }

  checkLogin(url: string): boolean {
    if (this.authService.isLoggedIn) { return true; }

    // Store the attempted URL for redirecting
    this.authService.redirectUrl = url;

    // Create a dummy session id
    let sessionId = 123456789;

    // Set our navigation extras object
    // that contains our global query params and fragment
    let navigationExtras: NavigationExtras = {
      queryParams: { 'session_id': sessionId },
      fragment: 'anchor'
    };

    // Navigate to the login page with extras
    this.router.navigate(['/login'], navigationExtras);
    return false;
  }
}

CanActivate, CanActivateChild, CanLoad 这三个是说是否能进入子路由。

我们来看这个过程:

canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    return this.canActivate(route, state);
  }
 //这是第一步,能否激活子路由。

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    let url: string = state.url;
    return this.checkLogin(url);
  }

checkLogin(url: string): boolean {
    if (this.authService.isLoggedIn) { return true; }

    // Store the attempted URL for redirecting  初次进来isLoggedIn是false, 会走这里 
    this.authService.redirectUrl = url;

    // Create a dummy session id
    let sessionId = 123456789;

    // Set our navigation extras object
    // that contains our global query params and fragment
    let navigationExtras: NavigationExtras = {
      queryParams: { 'session_id': sessionId },
      fragment: 'anchor'
    };

    // Navigate to the login page with extras
    this.router.navigate(['/login'], navigationExtras);
    return false;
  }

初次进来,会进canload方法,获取到当前的url为'/admin'被拦截,通过this.router.navigate(['/login'], navigationExtras)定位到login的组件页面。
注意canload只会在页面首次请求的那一次才有。

执行顺序:canload(only once) -> canActivate -> canActivateChild

Auth.service代码:

import { Injectable } from '@angular/core';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/delay';

@Injectable()
export class AuthService {
  isLoggedIn = false;   // 提供的登录状态为false

  // store the URL so we can redirect after logging in
  redirectUrl: string;

  login(): Observable {
    return Observable.of(true).delay(1000).do(val => this.isLoggedIn = true);
  }

  logout(): void {
    this.isLoggedIn = false;
  }
}

根据以上我们首次点击admin,此时还处于未登录状态,我们会被带到login界面。


import { Component }        from '@angular/core';
import { Router, NavigationExtras } from '@angular/router';
import { AuthService }      from './auth.service';

@Component({
  template: `
    

LOGIN

{{message}}

` }) 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) { // Get the redirect URL from our auth service // If no redirect has been set, use the default let redirect = this.authService.redirectUrl ? this.authService.redirectUrl : '/admin'; // Set our navigation extras object // that passes on our global query params and fragment let navigationExtras: NavigationExtras = { queryParamsHandling: 'preserve', preserveFragment: true }; // Redirect the user this.router.navigate([redirect], navigationExtras); } }); } logout() { this.authService.logout(); this.setMessage(); } }

初次进入login未登录,message='logged out',点击login会执行authService的login方法,改变登录状态。

if (this.authService.isLoggedIn) {
        // Get the redirect URL from our auth service
        // If no redirect has been set, use the default
        let redirect = this.authService.redirectUrl ? this.authService.redirectUrl : '/admin';

        // Set our navigation extras object
        // that passes on our global query params and fragment 保留之前的设置
        let navigationExtras: NavigationExtras = {
          queryParamsHandling: 'preserve',
          preserveFragment: true
        };

        // Redirect the user
        this.router.navigate([redirect], navigationExtras);
      }

之前我们在auth.service中设置了redirecturl为admin,所以会navigate到admin界面,并携带navigationExtras。

const adminRoutes: Routes = [
  {
    path: '',
    component: AdminComponent,
    canActivate: [AuthGuard],
    children: [
      {
        path: '',
        canActivateChild: [AuthGuard],
        children: [
          { path: 'crises', component: ManageCrisesComponent },
          { path: 'heroes', component: ManageHeroesComponent },
          { path: '', component: AdminDashboardComponent }
        ]
      }
    ]
  }
];

好,现在成功进入admin,默认会加载AdminDashboardComponent。

import { Component, OnInit }    from '@angular/core';
import { ActivatedRoute }       from '@angular/router';
import { Observable }           from 'rxjs/Observable';

import { SelectivePreloadingStrategy } from '../selective-preloading-strategy';

import 'rxjs/add/operator/map';

@Component({
  template:  `
    

Dashboard

Session ID: {{ sessionId | async }}

Token: {{ token | async }}

Preloaded Modules
  • {{ module }}
` }) export class AdminDashboardComponent implements OnInit { sessionId: Observable; token: Observable; modules: string[]; constructor( private route: ActivatedRoute, private preloadStrategy: SelectivePreloadingStrategy ) { this.modules = preloadStrategy.preloadedModules; } ngOnInit() { // Capture the session ID if available this.sessionId = this.route .queryParamMap .map(params => params.get('session_id') || 'None'); // Capture the fragment if available this.token = this.route .fragment .map(fragment => fragment || 'None'); } }

ngoninit的数据就是从auth.service里面原封不动过来的。。。

最后说一下:

loadChildren: 'app/admin/admin.module#AdminModule'

这个是加载的子模块名。感觉这一期讲不完,下一期再继续分享吧。。。

你可能感兴趣的:(Angular路由深入讲解)